Overlap in d3 wordcloud

I am using Jason Davis's wordcloud library for d3 ( https://github.com/jasondavies/d3-cloud ) and my problem is that the words in the cloud overlap.

I know that there are already questions regarding this problem when the stack overflows (and other sites), but none of this helped in my case.

In the following example, I use the cloud example from the Jason Davis website and changed only a few things:

  • I read my words and their sizes from an external file.
  • I set the rotation to 0. The angle of rotation does not seem to matter.
  • I commented on the Impact font to rule out any font loading problems. (It doesn't make any difference, though.)

Here is my code:

<!DOCTYPE html> <meta charset="utf-8"> <body> <script src="d3.js"></script> <script src="d3.layout.cloud.js"></script> <script> d3.tsv("testdata.txt", function(error, data) { var fill = d3.scale.category20(); d3.layout.cloud().size([300, 300]) .words(data) .padding(1) .rotate(function(d) { return 0; }) // .font("Impact") .fontSize(function(d) { return d.size; }) .on("end", draw) .start(); function draw(words) { d3.select("body").append("svg") .attr("width", 300) .attr("height", 300) .append("g") .attr("transform", "translate(150,150)") .selectAll("text") .data(words) .enter().append("text") .style("font-size", function(d) { return d.size + "px"; }) // .style("font-family", "Impact") .style("fill", function(d, i) { return fill(i); }) .attr("text-anchor", "middle") .attr("transform", function(d) { return "translate(" + [dx, dy] + ")rotate(" + d.rotate + ")"; }) .text(function(d) { return d.word; }); } } ) </script> 

The testdata identifier looks like this (color information is not used in the example):

 word size color der 39 #a9a9a9 die 37 #a9a9a9 und 30 #a9a9a9 athenischen 29 #a9a9a9 Die 29 #a9a9a9 eine 28 #a9a9a9 , 27 #a9a9a9 einer 26 #a9a9a9 attischen 26 #a9a9a9 liberalen 26 #1e90ff zur 25 #a9a9a9 athenische 24 #a9a9a9 christliche 23 #a9a9a9 attische 23 #a9a9a9 _START_ 22 #a9a9a9 reinen 22 #a9a9a9 englischen 21 #a9a9a9 oder 21 #a9a9a9 -- 21 #a9a9a9 radikalen 21 #a9a9a9 Q*M 21 #a9a9a9 Q*M 21 #a9a9a9 christlichen 20 #a9a9a9 schΓΆne 20 #1e90ff reprΓ€sentativen 20 #a9a9a9 sozialen 20 #a9a9a9 hellenische 19 #1e90ff modernen 19 #a9a9a9 radikale 19 #a9a9a9 griechische 19 #a9a9a9 - 18 #a9a9a9 schΓΆnen 18 #1e90ff alle 18 #a9a9a9 radicalen 18 #a9a9a9 als 17 #a9a9a9 neuen 17 #a9a9a9 perikleischen 16 #a9a9a9 bΓΌrgerlichen 16 #a9a9a9 Namen 16 #1e90ff 

If I run a js script with test data, my word cloud comes out with overlaps. Sometimes this happens only after a few reboots, but it is quite common.

Other people reported the same issue and found that it was related to using web fonts or skipping the rotate parameter. This is not applicable in my example.

I suspect this may be due to the fact that there are a lot of words for the canvas size, however I also tested where I significantly increased the canvas size, and this still happened (although less often, as the random placement of the word made it less likely). In addition to this, you can see that a few words are not displayed at all due to the small size of the canvas. Why leave someone and create overlap for others? Therefore, I think the problem lies elsewhere.

Any ideas?

Thanks!

+7
source share
3 answers

In the end, I asked Jason Davis himself, and in fact it was a rather simple mistake: you must specify the text access function in the first statement (not only in the draw function). It works if you add one line as follows:

 d3.layout.cloud().size([300, 300]) .words(data) .padding(1) .rotate(function(d) { return 0; }) // .font("Impact") .text(function(d) { return d.word; }) // THE SOLUTION .fontSize(function(d) { return d.size; }) .on("end", draw) .start(); 
+16
source

I tried a sample that you could play with, please see. wordcloud without overlap

essentially:

 <div id="cloud"></div> // First define your cloud data, using `text` and `size` properties: var fill = d3.scale.category20(); var words = { "Battery Related": "52382", "Billing": "52412", "Break Related": "52490", "Chain Related": "52471", "Clutch Related": "52468", "Dealer attitude": "52488", "Electrical Related": "52352", "Engine Related": "52446", "Handle Bar Related": "52486", "Happy": "52472", "Jerking": "52325", "Jerking Problem": "52325", "Low Mileage": "52489", "Noise": "52462", "Poor Pickup": "52406", "Running Off": "52242", "Service Quality": "52488", "Silencer Problem": "52468", "Starting Trouble": "52490", "Suspension Related": "52365", "Vehicle Noise": "52467", "Vibration": "52463", "Washing": "52488" }; var max_freq = 52490; var cloudwords = ["Battery Related", "Billing", "Break Related", "Chain Related", "Clutch Related", "Dealer attitude", "Electrical Related", "Engine Related", "Handle Bar Related", "Happy", "Jerking", "Jerking Problem", "Low Mileage", "Noise", "Poor Pickup", "Running Off", "Service Quality", "Silencer Problem", "Starting Trouble", "Suspension Related", "Vehicle Noise", "Vibration", "Washing"]; var url = 'http://xxx.yyyy.zz.ww/?q=abc/'; var width = 800, height = 800; var leaders = cloudwords .map(function(d) { return { text: d, size: 5 + (words[d] / max_freq) * 0.9 * 30 // *the size of the "box" occupied by each word. has no relation to text size. }; }) .sort(function(a, b) { return d3.descending(a.size, b.size) }); var leaderScale = d3.scale.linear().range([1, 20]); // *scale range to plot the relative sizes of the words. leaderScale.domain([d3.min(leaders, function(d) { return d.size; }), d3.max(leaders, function(d) { return d.size; }) ]); // Next you need to use the layout script to calculate the placement, rotation and size of each word: d3.layout.cloud().size([width, height]) .words(leaders) .padding(0) //fiddle with padding here, does not really have any effect on overlap. .rotate(function() { return ~~0; //to keep the words horizontal }) .font("Impact") .fontSize(function(d) { return d.size; }) .on("end", drawCloud) .start(); function drawCloud(words) { d3.select("#cloud").append("svg") .attr("width", width) .attr("height", height) .attr("text-align", "center") .append("g") .attr("transform", "translate(" + [width >> 1, height >> 1] + ")") //for transalting words to their different postions. .selectAll("text") .data(words) .enter().append("text") .style("font-size", function(d) { return leaderScale(d.size) + "px"; //used scale to resize words to a linear scale. }) .style("font-family", "Impact") .style("fill", function(d, i) { return fill(i); }) .attr("text-anchor", "middle") .attr("transform", function(d) { return "translate(" + [dx, dy] + ")rotate(" + d.rotate + ")"; }) .text(function(d) { return d.text; }) .on("click", function(d, i) { window.open(url + d.text); }); } // set the viewbox to content bounding box (zooming in on the content, effectively trimming whitespace) var svg = document.getElementsByTagName("svg")[0]; var bbox = svg.getBBox(); var viewBox = [bbox.x, bbox.y, bbox.width, bbox.height].join(" "); svg.setAttribute("viewBox", viewBox); 
0
source

The author fix does not work for me. What worked was to specify .font() in the cloud layout setting and declare the same font family in the rendering code - ironically, the fragment the author commented on above. This gives the code a font for sizing. Without this, a better guess is required.

 d3.layout.cloud().size([800, 400]) .words(words) .font('Impact') // <-- what mattered .fontSize(d => d.size) .on('end', draw) .start() 

You also need to specify the font family in the draw() function so that the cloud displays the words in the correct font that matches the one declared above:

 .attr('font-family', 'Impact') 
0
source

All Articles