Hi.
I'm trying to produce a wordcloud based on twitter hashtags with Jason Davies' D3 wordcloud visualization (https://www.jasondavies.com/wordcloud/)
My problem is that I haven't yet fully understood how to properly implement the concept of data entry, update and exit (http://bost.ocks.org/mike/join/). I understand the idea, I just don't see why it doesn't work in my example.
I know how to retrieve the search results, and I know how to generate the word cloud initially, but I fail at updating the word cloud - that is, removing words that are no longer in the results and adding new ones (when I change the search).
Here is my (lengthy) code:
var search = splunkjs.mvc.Components.getInstance("search1"); // get the search manager
var myResults = search.data("results"); // get the data from that search
var resultArray = []; // prepare array to hold rows
var searchresult_tags = []; // array for hashtags
var searchresult_weight = []; // array for corresponding importance
var duration = 750; // duration for transitions
var fontSizeRange = d3.scale.linear(); // a scale to map importance to pixels
// A group in which to present the cloud
var cloudGroup = d3.select("#wordcloud").append("svg")
.attr("width", 600)
.attr("height", 600)
.append("g")
.attr("transform", "translate(300,300)");
// When data arrives:
myResults.on("data", function() {
// Fill two arrays with results (tags and weight)
resultArray = myResults.data().rows;
for (i = 0; i < resultArray.length; i++) {
searchresult_tags[i] = resultArray[i][0];
searchresult_weight[i] = parseInt(resultArray[i][1]);
}
// Dynamically calculate the font sizes
fontSizeRange
.domain([searchresult_weight[9], searchresult_weight[0]])
.range([8, 35]);
// And start to layout the cloud
d3.layout.cloud()
.size([600, 600])
.words(searchresult_tags.map(function(d, i) {
var result = {text: searchresult_tags[i], size: searchresult_weight[i]};
return result;
}))
.padding(1)
.rotate(function() { return ~~(Math.random() * 70) - 35 ; })
.font("Impact")
.fontSize(function(d) { return fontSizeRange(d.size); })
.on("end", draw)
.start();
// This function placed here to increase readability
function draw(words) {
cloudData = cloudGroup
.selectAll("text")
.data(words);
// This part should deal with (newly) entering data
cloudData.enter().append("text")
.style("font-size", "0px")
.style("font-family", "Impact")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.transition().duration(duration).style("font-size", function(d) { return d.size + "px"; });
// This with updating existing data
cloudData.transition().duration(duration).style("font-size", function(d) { return d.size + "px"; });
// And this with exiting unused data
cloudData.exit()
.transition().duration(duration).style("font-size", "0px")
.remove();
}
});
As I said, this works fine initially, and when I change the search and the new results come in, the font size of the existing words is adjusted - the words themselves just don't change. My guess is that something is wrong with the way I'm trying to update my data, but I can't seem to figure out how to do it properly.
Thanks for your efforts.
Ha, I got it.
The problem was not with the data, but with the display: to change the text that the wordcloud displays, you need to use the .text(...)
operator (duh). In my particular case, I changed my function draw(words)
to the following, altough you can certainly do it in other ways as well.
function draw(words) {
cloudData = cloudGroup
.selectAll("text")
.data(words);
// This part should deal with (newly) entering data
cloudData.enter().append("text")
.style("font-size", "0px")
.style("font-family", "Impact")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.transition().duration(duration).style("font-size", function(d) { return d.size + "px"; });
// This with updating existing data
cloudData
.transition().duration(duration).style("font-size", "0px")
.remove()
.style("font-size", "0px")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.transition().duration(duration).style("font-size", function(d) { return d.size + "px"; });
}
Ha, I got it.
The problem was not with the data, but with the display: to change the text that the wordcloud displays, you need to use the .text(...)
operator (duh). In my particular case, I changed my function draw(words)
to the following, altough you can certainly do it in other ways as well.
function draw(words) {
cloudData = cloudGroup
.selectAll("text")
.data(words);
// This part should deal with (newly) entering data
cloudData.enter().append("text")
.style("font-size", "0px")
.style("font-family", "Impact")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.transition().duration(duration).style("font-size", function(d) { return d.size + "px"; });
// This with updating existing data
cloudData
.transition().duration(duration).style("font-size", "0px")
.remove()
.style("font-size", "0px")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.transition().duration(duration).style("font-size", function(d) { return d.size + "px"; });
}