Skip to content

Instantly share code, notes, and snippets.

@dceejay
Last active June 28, 2019 15:36
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dceejay/1d9b0959cf7cc36f9f8c to your computer and use it in GitHub Desktop.
Save dceejay/1d9b0959cf7cc36f9f8c to your computer and use it in GitHub Desktop.
Dynamic Word Cloud

This flow aids in the generation of a dynamic Word Cloud type graphic. Currently it takes a Twitter feed as input, collects the last 20 tweets and uses d3 to generate a Word Cloud based on the relative frequency of words found... Great for getting a view on what's happening at Wimbledon, WorldCup etc etc...

As well as the flow you also need to download and save this web page

https://gist.githubusercontent.com/dceejay/1d9b0959cf7cc36f9f8c/raw/3bb9263537d996031d2337fd64582a323a5e9168/cloud.html

and the d3 word cloud library from here

https://github.com/jasondavies/d3-cloud

You just need the d3.layout.cloud.js (and the license :-)

Place these 2 (or 3) files into the node-red/public directory then fire up the flow below. Then browse to http://localhost:1880/cloud.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>Node-RED Wordcloud</title>
<body align=middle>
<script src="./d3.v3.min.js"></script>
<script src="./d3.layout.cloud.js"></script>
<script>
function wordCloud(selector) {
var fill = d3.scale.category20b();
var svg = d3.select(selector).append("svg")
.attr("width", 1024)
.attr("height", 768)
.append("g")
.attr("transform", "translate(512,384)");
function draw(words) {
//Use the 'text' attribute (the word itself) to identity unique elements.
var cloud = svg.selectAll("g text")
.data(words, function(d) { return d.text; })
//Entering words
cloud.enter()
.append("text")
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr('font-size', 2)
.text(function(d) { return d.text; });
//Entering and existing words
cloud.transition()
.duration(600)
.style("font-size", function(d) { return d.size + "px"; })
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.style("fill-opacity", 1);
//Exiting words
cloud.exit()
.transition()
.duration(200)
.style('fill-opacity', 1e-6)
.attr('font-size', 1)
.remove();
}
return {
//Recompute the word cloud for a new set of words. This method will
// asynchronously call draw when the layout has been computed.
update: function(words) {
d3.layout.cloud().size([1024, 768])
.words(words)
.padding(2)
.rotate(function() { return ~~(Math.random() * -2) * 90; })
//.rotate(function(d) { return ~~(Math.random() * 5) * 30 - 60; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
}
}
var myWordCloud = wordCloud('body');
var ws;
var server = window.location.hostname;
var wsUri = "ws://"+server+":1880/ws/wordcloud";
function start(wsUri) { // Create the websocket
ws = new WebSocket(wsUri);
ws.onopen = function(evt) {
console.log("CONNECTED");
//document.getElementById("foot").innerHTML = "<font color='#494'>"+ibmfoot+"</font>";
//ws.send("Open for mapping");
};
ws.onclose = function(evt) {
console.log("DISCONNECTED");
//document.getElementById("foot").innerHTML = "<font color='#944'>"+ibmfoot+"</font>";
setTimeout(function(){ start(wsUri) }, 3000); // try to reconnect every 3 secs... bit fast ?
}
// This expects a websocket message with data as a stringified object containing at least name, lat and lon
ws.onmessage = function (evt) {
//console.log("DATA",evt.data);
var wds = JSON.parse(evt.data);
keysSorted = Object.keys(wds).sort(function(a,b){return wds[a]-wds[b]});
//console.log(keysSorted);
var wrds=[];
for (var key in wds) {
wrds.push({ text:key, size:10 + 20 * wds[key] });
}
myWordCloud.update(wrds);
}
ws.onerror = function(evt) {
console.log("ERROR",evt);
//document.getElementById("foot").innerHTML = "<font color='#f00'>"+ibmfoot+"</font>";
}
}
start(wsUri);
</script>
</body>
[{"id":"2e79fb74.d18604","type":"websocket-listener","path":"/ws/wordcloud","wholemsg":"false"},{"id":"971c3cb4.68e3c","type":"twitter in","twitter":"","tags":"Wimbledon","user":"false","name":"","topic":"tweets","x":95,"y":860,"z":"be2ce773.41d318","wires":[["82800f44.7d7ff","db1d2cd.f24e2d"]]},{"id":"db1d2cd.f24e2d","type":"function","name":"Filter Tweets","func":"\nvar tweets = context.tweets || [\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"];\n\nif (msg.tweet.lang == \"en\") {\n\n\tvar foo = msg.tweet.text;\n\tif (foo.indexOf(\"http\") != -1) {\n\t\tfoo = (foo.split(\"http\"))[0];\n\t}\n\tif (foo.toLowerCase().indexOf(\"job\") != -1) return null;\n\tfoo = foo.replace(/[^\\x00-\\x7F]/g, \"\");\n\t\n\tif (tweets.indexOf(foo) == -1) {\n\t\ttweets.shift();\n\t\ttweets.push(foo);\n\t\t//console.log(tweets);\n\t\tcontext.tweets = tweets;\n\t\treturn {payload:tweets};\n\t}\n}\nreturn null;\n","outputs":1,"x":231,"y":915,"z":"be2ce773.41d318","wires":[["a021049c.5fdef8"]]},{"id":"a021049c.5fdef8","type":"delay","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"12","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":338,"y":862,"z":"be2ce773.41d318","wires":[["ec4252b1.13bdb"]]},{"id":"ec4252b1.13bdb","type":"function","name":"Count words in array","func":"var index = {};\n\nvar garbage = [ \"and\", \"for\", \"but\", \"the\", \"&lt\", \"&gt\", \"his\", \"her\", \"pre\", \"are\", \"&amp\", \"with\", \"fuck\", \"shit\", \"crap\", \"cunt\"];\n\nfunction countWords(sentence) {\n\twords = sentence\n\t\t.replace(/[.,?!;()\"'-]/g, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t//.toLowerCase()\n\t\t.split(\" \");\n\t\n\twords.forEach(function (word) {\n\t\tif ((word.length > 2)&&( isNaN(Number(word)) )&&( garbage.indexOf(word.toLowerCase()) == -1 )) {\n\t\t if (!(index.hasOwnProperty(word))) {\n\t\t index[word] = 0;\n\t\t }\n\t\t index[word]++;\n\t\t}\n\t});\n\t\n\t//console.log(Object.keys(index).length);\n\t//return index;\n}\n\nfor (var i in msg.payload) {\n\tcountWords(msg.payload[i]);\n}\n\nmsg.payload = JSON.stringify(index);\n\nreturn msg;","outputs":1,"x":461,"y":918,"z":"be2ce773.41d318","wires":[["8d368567.72c978","ae9655e4.5169a8"]]},{"id":"8d368567.72c978","type":"websocket out","name":"","server":"2e79fb74.d18604","x":600,"y":850,"z":"be2ce773.41d318","wires":[]},{"id":"ae9655e4.5169a8","type":"debug","name":"","active":false,"console":"false","complete":"false","x":651,"y":918,"z":"be2ce773.41d318","wires":[]}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment