Dashboards & Visualizations

How to edit my D3 custom visualization code to incorporate data entry, update and exit to produce a wordcloud based on twitter hashtags?

jeffland
Champion

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.

0 Karma
1 Solution

jeffland
Champion

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"; });
}

View solution in original post

jeffland
Champion

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"; });
}

View solution in original post

.conf21 CFS Extended through 5/20!

Don't miss your chance
to share your Splunk
wisdom in-person or
virtually at .conf21!

Call for Speakers has
been extended through
Thursday, 5/20!