d3词云中的重叠问题

8
我使用Jason Davies的d3词云库(https://github.com/jasondavies/d3-cloud),但我的问题是云中的单词重叠。
我知道在stackoverflow(和其他网站)上已经有了关于这个问题的问题,但这些问题都没有对我的情况有所帮助。
在下面的例子中,我使用了Jason Davies网站上的示例云,并仅改变了一些内容:
  • 我从外部文件中读取我的单词及其大小。
  • 我将旋转设置为0。但旋转角度似乎没有什么区别。
  • 我注释掉了"Impact"字体,以排除字体加载问题。(然而也没有任何区别。)
这是我的代码:
<!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(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
        })
        .text(function(d) { return d.word; });
  }
  }
  )

</script>

测试数据如下(颜色信息在示例中未被使用):
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

如果我用测试数据运行js脚本,我的词云会出现重叠。有时候只是在几次重新加载之后才会发生,但这是相当频繁的。
其他人报告了同样的问题,并发现这与使用网络字体或跳过旋转参数有关。但这不适用于我的例子。
我怀疑这可能与画布大小和单词数量有关,但我也做了一些测试,明显增加了画布大小,它仍然发生(尽管不太频繁,因为单词的随机放置使其不太可能)。除此之外,您可以看到由于画布大小的限制,有几个单词根本没有显示。为什么要遗漏一些单词以及给其他单词造成重叠呢?所以我认为问题出在其他地方。
有任何想法吗?
谢谢!

虽然存在一些困难,但是通过对维度(布局和SVG)和字体族进行微调,可以偶尔出现相对较轻的重叠(但这些重叠是否可接受可能有点主观)。无论如何,这里有一个plunk,我创建它来测试。至少,我认为这些维度改进了单词排除...看起来大部分单词都在那里(尽管需要全部在那里)。 - FernOfTheAndes
是的,谢谢。你改变了尺寸和字体,我看到了。对于这个例子来说,它确实运行得很好。然而,我真的想通常解决这个问题。我其实很惊讶它会发生,因为词云布局的整个重点应该是避免重叠,我看到了很多对它的赞扬。 - spaebrun
你说得对。那就是我所做的一切。我同意你的看法,需要一个通用解决方案。我们可能会遗漏了什么......我还没有时间深入研究它。你也可以给Jason写封电子邮件。 - FernOfTheAndes
3个回答

19

我最终问了Jason Davies自己,问题实际上很简单:您必须在第一条语句中指定文本访问器函数(不仅在“draw”函数中)。如果您添加以下一行,则可以正常工作:

你需要在第一条语句中指定文本访问器函数(不仅在“draw”函数中)。添加下面这行代码即可:

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();

不,它只是调整了屏幕上单词的分布并使方框变得更小,导致更多重叠。 - dresh
常见问题:未将SVG文本锚定到中心,.attr("text-anchor", "middle") - Alok Pepakayala
问题在我的情况下仍然存在。 - bumbeishvili

4
作者提供的方法对我无效。而真正有效的方法是在云端布局设置中明确指定.font(),并在绘制代码中声明相同的字体家族 -讽刺的是,作者上面注释掉的那部分代码。这可以为代码提供计算尺寸所需的字体。如果没有它,程序会进行最佳猜测。
d3.layout.cloud().size([800, 400])
  .words(words)
  .font('Impact') // <-- what mattered
  .fontSize(d => d.size)
  .on('end', draw)
  .start()

您还需要在draw()函数中指定字体系列,以便云朵使用与上面声明的字体匹配的正确字体来呈现单词:

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

1

我为你准备了一个样例,可以让你尝试一下, 请看一下。 无重叠词云

本质上:

<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(" + [d.x, d.y] + ")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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接