spedr
spedr

Reputation: 23

Redrawing word cloud with d3.wordcloud

I'm using d3.wordcloud (https://github.com/wvengen/d3-wordcloud) and I'm trying to erase my current word cloud and draw a new one on button click. It works nicely for the first time the word cloud is drawn, but on each successive draw the font sizes get more and more out of order.

Here's how it looks on the first run and after a couple successive runs:

first run

last run

Upon observation, I can see that the range of font sizes diminishes after each run, ultimately setting each word with a size of either 10 or 100. I believe the problem is happening in the function update in d3.wordcloud.js, but I'm not sure how to go on about solving this problem.

  function update() {
     var words = layout.words();
     fontSize = d3.scale[scale]().range([10, 100]);

     if (words.length) {
         fontSize.domain([+words[words.length - 1].size || 1, +words[0].size]);
       }
  }

The code I'm running is based on the example provided in the repository, with a smaller word object for testing purposes and a button for redrawing the code.

And here is a live demo:

var test_words = [
        {text: 'have', size: 102},
        {text: 'Oliver', size: 47},
        {text: 'say', size: 46},
        {text: 'said', size: 36},
        {text: 'bumble', size: 29, href: 'https://en.wikipedia.org/wiki/Beadle'},
        {text: 'will', size: 29},
        {text: 'Mrs', size: 56, href: 'https://en.wikipedia.org/wiki/Mrs.'},
        {text: 'Mann', size: 27, href: 'http://educationcing.blogspot.nl/2012/06/oliver-twist-mrs-manns-character.html'},
        {text: 'Mr', size: 27},
        {text: 'very', size: 26},
        {text: 'child', size: 20},
        {text: 'all', size: 19},
        {text: 'boy', size: 19},
        {text: 'gentleman', size: 19, href: 'http://www.thefreelibrary.com/The+gentleman+in+the+white+waistcoat%3a+Dickens+and+metonymy.-a0154239625'},
        {text: 'great', size: 19},
        {text: 'take', size: 19},
        {text: 'but', size: 18},
        {text: 'beadle', size: 16},
        {text: 'twist', size: 16},
        {text: 'board', size: 15},
        {text: 'more', size: 15},
        {text: 'one', size: 15}
      ];
      
d3.wordcloud()
        .size([600, 275])
        .transitionDuration(1000)
        .fill(d3.scale.ordinal().range(["#884400", "#448800", "#888800", "#444400"]))
        .words(test_words)
        .onwordclick(function(d, i) {
          if (d.href) { window.location = d.href; }
        })
        .start();

      d3.select('#dataset-picker').selectAll('.dataset-button')
        .on("click", function() {
          //clear the wordcloud div
          document.getElementById("wordcloud").innerHTML = "";
          var a2 = d3.wordcloud()
            .size([600, 275])
            .transitionDuration(1000)
            .fill(d3.scale.ordinal().range(["#884400", "#448800", "#888800", "#444400"]))
            .words(test_words)
            .onwordclick(function(d, i) {
              if (d.href) { window.location = d.href; }
            })
            .start();
        });
<html>
  <head>
    <meta charset="UTF-8">
    <title>Word Cloud</title>
    <script src="https://cdn.rawgit.com/wvengen/d3-wordcloud/master/lib/d3/d3.js"></script>
    <script src="https://cdn.rawgit.com/wvengen/d3-wordcloud/master/lib/d3/d3.layout.cloud.js"></script>
    <script src="https://cdn.rawgit.com/wvengen/d3-wordcloud/master/d3.wordcloud.js"></script>
    <!-- <script src="test_words.js"></script> -->
  </head>
  <body style="text-align: center">
    <h1>Word Cloud</h1>
    <div id='wordcloud'></div>
    <div id="dataset-picker">
      <input id ="test" value="test" class="dataset-button" type="button">
    </div>
      </body>
</html>

Thank you.

Upvotes: 2

Views: 1350

Answers (1)

Xavier Guihot
Xavier Guihot

Reputation: 61666

This is bit tricky. The d3-wordcloud plugin (more exactly the underlying d3.layout.cloud plugin) will modify your input dataset of words to adapt the size of the words.

Little by little certain words will be bigger and bigger and others smaller and smaller.

To avoid this, you can provide the plugin each time you create a new cloud with a deep copy of the original dataset. This way the original dataset always remains untouched:

Here is a way to deep-copy a json with javascript:

function deepCopy(oldValue) { 
  var newValue
  strValue = JSON.stringify(oldValue)
  return newValue = JSON.parse(strValue)
}

which you pass to the wordcloud plugin this way:

.words(deepCopy(test_words))

And here is a demo:

var test_words = [
        {text: 'have', size: 102},
        {text: 'Oliver', size: 47},
        {text: 'say', size: 46},
        {text: 'said', size: 36},
        {text: 'bumble', size: 29, href: 'https://en.wikipedia.org/wiki/Beadle'},
        {text: 'will', size: 29},
        {text: 'Mrs', size: 56, href: 'https://en.wikipedia.org/wiki/Mrs.'},
        {text: 'Mann', size: 27, href: 'http://educationcing.blogspot.nl/2012/06/oliver-twist-mrs-manns-character.html'},
        {text: 'Mr', size: 27},
        {text: 'very', size: 26},
        {text: 'child', size: 20},
        {text: 'all', size: 19},
        {text: 'boy', size: 19},
        {text: 'gentleman', size: 19, href: 'http://www.thefreelibrary.com/The+gentleman+in+the+white+waistcoat%3a+Dickens+and+metonymy.-a0154239625'},
        {text: 'great', size: 19},
        {text: 'take', size: 19},
        {text: 'but', size: 18},
        {text: 'beadle', size: 16},
        {text: 'twist', size: 16},
        {text: 'board', size: 15},
        {text: 'more', size: 15},
        {text: 'one', size: 15}
      ];

      function deepCopy(oldValue) { 
        var newValue
        strValue = JSON.stringify(oldValue)
        return newValue = JSON.parse(strValue)
      }

      var cloud = d3.wordcloud()
        .size([500, 275])
        .transitionDuration(1000)
        .fill(d3.scale.ordinal().range(["#884400", "#448800", "#888800", "#444400"]))
        .words(deepCopy(test_words))
        .onwordclick(function(d, i) {
          if (d.href) { window.location = d.href; }
        })
        .start();

      d3.select('#dataset-picker').selectAll('.dataset-button')
        .on("click", function() {
          //clear the wordcloud div
          // cloud.remove();
          document.getElementById("wordcloud").innerHTML = "";
          var cloud = d3.wordcloud()
            .size([500, 275])
            .transitionDuration(1000)
            .fill(d3.scale.ordinal().range(["#884400", "#448800", "#888800", "#444400"]))
            .words(deepCopy(test_words))
            .onwordclick(function(d, i) {
              if (d.href) { window.location = d.href; }
            })
            .start();
        });
<html>
  <head>
    <meta charset="UTF-8">
    <title>Word Cloud</title>
    <script src="https://cdn.rawgit.com/wvengen/d3-wordcloud/master/lib/d3/d3.js"></script>
    <script src="https://cdn.rawgit.com/wvengen/d3-wordcloud/master/lib/d3/d3.layout.cloud.js"></script>
    <script src="https://cdn.rawgit.com/wvengen/d3-wordcloud/master/d3.wordcloud.js"></script>
  </head>
  <body style="text-align: center">
    <h1>Word Cloud</h1>
    <div id='wordcloud'></div>
    <div id="dataset-picker">
      <input id ="test" value="test" class="dataset-button" type="button">
    </div>
      </body>
</html>

Upvotes: 3

Related Questions