jasondavies / d3-cloud

Create word clouds in JavaScript.
https://www.jasondavies.com/wordcloud/
Other
3.8k stars 1.08k forks source link

Most important words (highest counts) are excluded if they don't fit #36

Open john-guerra opened 10 years ago

john-guerra commented 10 years ago

Right now the algorithm leaves out words that are too big to fit on the available width/height. This is OK for smaller words that aren't that important, but it also leaves out the most important words on a cloud without any warning.

http://jsfiddle.net/duto_guerra/VNurQ/

I can think of two solutions:

  1. If the word doesn't fit, draw it at least partially.
  2. Adjust the size range to guarantee that the biggest word will always fit

I was planning on trying to do this myself and send you a pull request, but I would like to hear your preferences and opinion

John

lauvil commented 9 years ago

We are running into this problem too. I think either solution suggested above would work. Has anyone implemented one of them? Although these words are often the highest ranking ones, they could occur anywhere in the list.

john-guerra commented 9 years ago

I implemented an overflow option here https://github.com/john-guerra/d3-cloud

tribe84 commented 9 years ago

@john-guerra I tried to replace my version of the layout with your version, and while it does work the scaling and arrangement seem a bit off.

It does not seem to take all available width into account.

See pictures below to see what I mean.

image

image

john-guerra commented 9 years ago

@tribe84 your solution looks cool, have you posted your code somewhere?

sedenardi commented 9 years ago

@john-guerra nice job on the overflow code.

john-guerra commented 9 years ago

Thanks a lot!

On Mon Jan 26 2015 at 12:54:19 PM Sanders DeNardi notifications@github.com wrote:

@john-guerra https://github.com/john-guerra nice job on the overflow code.

— Reply to this email directly or view it on GitHub https://github.com/jasondavies/d3-cloud/issues/36#issuecomment-71536165.

xellos86 commented 9 years ago

Hi, i've runned into this problem at first, too. But i noticed when you set a domain, the words won't get drawn bigger than your range value:

var fontScale = d3.scale.linear() .domain([words min size, words max size]) .range([min font size, max font size]);

See this fiddle for an example: http://jsfiddle.net/ndenmhhw/3/

While this doesn't change the Issue, that the words won't get drawn when they are bigger than the height/width, it let's you controll it through the size parameters.

hope this helps someone

vvitvits commented 9 years ago

I ran into the same problem as @tribe84. That forced me to rethink my approach to this issue. I ended up increasing the layout space in the place function, such that it is no longer checks sizing bounds but enlarging sizing bounds. Of course I scale my entire SVG on the subsequent draw method. I understand that this solution is not ideal but it seemed to work for me.

ghostsquad commented 9 years ago

+1 this feature is super needed

eformx commented 9 years ago

My fix was to create a maximum font size based on the largest size and scale all other words point size. I found that the words are always visible. If you want a larger size simply adjust the maxFontSize variable...

Example:

//add a variable or dynamically control this value from an input box var maxFontSize = 36;

d3.layout.cloud().size([width, height]) .words(tags.map(function (d) { return { text: d.text, size: d.size }; })) .padding(1) //.rotate(function () { return ~~(Math.random() * 2) * 60; }) .rotate(function (d) { return 0; }) .text(function (d) { return d.text; }) // THE SOLUTION .font("Impact") .fontSize(function (d) { //THE SOLUTION PART II //restrict font size to max of 36 //largest size / word current size * maxFontSize //Example: 1st word is Canada and size is 444. Calculation is 444/444 = 1 * 36 sets largest word to 36 (points) //Example: 2nd word is item and size is 327. Calculation is 327/444 = 0.734 * 36 = 26.5 (points) return eval(d.size / largest_size * maxFontSize); }) .on("end", draw) .start();

supaMUC commented 8 years ago

Having the same issue. If I have long words they are just left out depending on the position of the other words. Specifying a max font size is not really helping because short words are then very small even they could be a lot bigger...

+1

bartaelterman commented 8 years ago

+1 @supaMUC

@eformx I don't see how Canada would get a size of 444 in your example. If you set the largest font size to 36, how does your solution prevent that a really long word like blablablablablablabla would fall off?

ghostsquad commented 8 years ago

You need one more calculation in there.

Max size of div / # characters = max pixels per character then you would need to figure out the size/character for the font you are using at each font size.

This might help. http://websemantics.co.uk/resources/font_size_conversion_chart/

So, the above calculation will actually give you the max font sized based on the longest word with the highest value.

satotake commented 8 years ago

I found a way of fixing this issue.

I confirm that all words are rendered by the recursive strategy.

edited http://jsfiddle.net/ndenmhhw/3/

var words = [
        "Hello", "world", "normally", "you", "want", "more", "words",
        "than", "this"].map(function(d) {
        return {text: d, size: 10 + ~~(Math.random() * 90)};
        }).concat([{text:"OneBigWord", size: 150}]);

maxSize = d3.max(words, function(d) { return d.size; });
minSize = d3.min(words, function(d) { return d.size; });

function drawcloud (range_max) { // declare the function
var fontScale = d3.scale.linear()
  .domain([minSize, maxSize]) 
  .range([5, range_max]); // the argument here 

var fill = d3.scale.category20();
d3.layout.cloud().size([300, 300])
  .words(words)
  .padding(5)
  .rotate(function() { return ~~(Math.random() * 2) * 90; })
  .font("Impact")
  .fontSize(function(d) { return fontScale(d.size) }) 
  .on("end", function(output) {
      if (word.length !== output.length) {  // compare between input ant output
           drawcloud ( range_max - 5 ); // call the function recursively
           return undefined;  
      }
      else { draw(output); }     // when all words are included, start rendering
   })
  .start();

function draw(words) {
    d3.select("body").append("svg")
////
//// please refer http://jsfiddle.net/ndenmhhw/3/
////
}

Although it does not always fit your situations, it might help some people annoyed with this issue And I hope that this issue is solved in the d3-cloud.

axelson commented 8 years ago

@satotake that looks like a decent idea for a solution. Although the jsfiddle you link is currently broken and doesn't render anything. Probably best to load d3-cloud from github rather than jasondavies.com.

fallenartist commented 8 years ago

Building on examples above, I've successfully implemented sorting and sizing of the words by frequency of appearance with duplicate removal: http://codepen.io/znak/details/rOgXoV/

As for the max font size for the biggest word, it could be done with measureText() method.

BhumikaSarswat commented 8 years ago

hi @john-guerra i used your algorithm but its performance is not good. Kindly help me in that.

john-guerra commented 8 years ago

@BhumikaSarswat I'm sorry but your request is too vague

Frozenfire92 commented 8 years ago

Any update on this?

samkugji commented 7 years ago

@satotake @axelson I fix satotake's broken jsfiddle http://jsfiddle.net/rnz0khfx/

samkugji commented 7 years ago

@jasondavies I try to make that word size is bigger proportionately word frequency like cool, nice and fabulous jasondavies's (https://www.jasondavies.com/wordcloud/)

I really wonder how do I make it? Is it possible with this d3-cloud api?

Here is my best to make now below based on satotake's


function wordFrequency(words){
  var newArray = [];
  var wordObj;
  words.forEach(function(word) {
    wordObj = newArray.filter(function (w){
      return w.text == word;
    });
    if (wordObj.length) { wordObj[0].size += 1; }
    else { newArray.push({text: word, size: 1}); }
  });
  return newArray;
};

var words = $("#data").data("words");
var regExp = /[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
words = words.replace(regExp, '').split(' ');
words = wordFrequency(words).map(function(d) {
      return {text: d.text, size:50 * ( 1+Math.sqrt(d.size) )};
    })
wildgarden commented 7 years ago

Satotakes and samkugij jsfiddle did not include his proposed workaround using recursive calls. Here is a enhanced jsfiddle http://jsfiddle.net/ymmh9dLq/

owendall commented 6 years ago

Wow, I just stumbled on the same issue in 2018. Thanks for your posts... :-)

khushbu-maurya commented 4 years ago

I implemented an overflow option here https://github.com/john-guerra/d3-cloud

hello @john-guerra can you provide this expmple in d3 v5? if I am try to run this exaplme in d3 v5 its noy working

john-guerra commented 4 years ago

Sorry I don't have the cycles right now

John. From my phone, brevity and all http://johnguerra.co

On Tue, Oct 8, 2019, 9:24 AM khushbu notifications@github.com wrote:

I implemented an overflow option here https://github.com/john-guerra/d3-cloud

hello @john-guerra https://github.com/john-guerra can you provide this expmple in d3 v5? if I am try to run this exaplme in d3 v5 its noy working

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/jasondavies/d3-cloud/issues/36?email_source=notifications&email_token=AAJJCS5O4IC7QWSHBPUFBDTQNSJZZA5CNFSM4AOSCJC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAULIPQ#issuecomment-539538494, or mute the thread https://github.com/notifications/unsubscribe-auth/AAJJCS5WBUMTPILEFE6IZNLQNSJZZANCNFSM4AOSCJCQ .