发表于2018 年 2 月 1 日 

 

下载

该项目的源代码和二进制文件可在

https://github.com/BradSmith1985/TagClouds

标签云 javascript 标签云的作用_标签云

典型的标签云

标签云是一种显示主题/类别列表及其相对出现频率的机制。这对于在博客、新闻网站、讨论板、信息系统等上查找内容很有用。它们可以是文本的或图形的,尽管后者提供了更大的灵活性并且是本文的重点。

可以对标签云的布局进行很多思考。您会注意到此博客中使用的默认 WordPress 标签云只是按字母顺序列出标签,并使用逐渐变大的字体大小来表示更常用的标签。更复杂的布局尝试将最常用的标签放置在中心,最少使用的标签位于最远的位置。我什至为此目的探索了 力导向算法,尽管我后来得出结论认为它们不太适合这个问题。

我最终采用的解决方案采用迭代方法,并使用适应度标准来达到最佳布局。它将每个标签按从最高频率到最低频率的顺序放置在图表上。标签的位置仅由它之前的标签决定。从中心开始向外工作,选择一个位置,使标签不会与任何先前放置的标签发生碰撞。如果标签保持在该位置,则使用几个标准来计算解决方案的适应度。对所有方向(360 度)重复此过程。每次找到更优化的解决方案时,标签都会移动到该位置。当所有角度都用尽时,标签将处于最佳位置,并且控制移动到下一个标签。

健身标准如下:

  • 标签云的总面积越小越好
  • 标签云的平方*(即宽度和高度之间的差异最小)越多越好
  • 离标签云中心的距离越小越好

* 或其他一些纵横比

算法

  1. 按频率降序对标签集合进行排序。
  2. 让 totalBounds表示标签云的使用边界,并初始化为零。
  3. 对于集合中的 每个标签:
  1. 让 bestBounds 表示迄今为止最优解的边界,并初始化为零。
  2. 让 bestDist表示与标签云中心的距离,以获得最佳解决方案,并将其初始化为零。
  3. 对于从 0 到 360 度 的每个角度:
  1. tagBounds表示 tag 的边界(位置和大小),并在标签云的中心初始化
  2. tagDist表示距标签云中心的距离,并初始化为零。
  3. 重复以下操作:
  1. tagBounds从标签云中心以角度移动到一个点tagDist单位。
  2. 检查tagBounds是否与之前放置的任何标签的边界发生冲突(相交)。
  3. 如果有冲突,增加tagDist。否则,退出循环。
  1. 让 isBest表示以下是否全部为真:
  • totalBounds  ∪  tagBounds ) 的面积小于 bestBounds的面积(或者 bestBounds为空)
  • totalBounds  ∪  tagBounds )的宽高差小于bestBounds的宽 高差(或者 bestBounds为空)
  • tagDist小于 bestDist(或 bestDist为零)
  1. 如果 isBest为真:
  1. 将 bestBounds设置为 ( totalBounds  ∪  tagBounds )。
  2. 将 bestDist设置为 tagDist
  3. 将 标签移动到 bestBounds的位置。
  1. 将 totalBounds设置为 bestBounds

执行

我对上述算法的实现采用名为 的 C# 类的形式TagCloud,它表示标签云并提供将布局应用到屏幕或位图的方法。

包括指定基本视觉属性的能力,例如字体、颜色和样式,以及具有最高和最低频率的标签字体大小之间的渐变。我还提供了一种支持特定纵横比的方法(因为不是每个人都想要一个完美的方形标签云),尽管应该注意最终标签云的纵横比将介于所需值和 1:1(方形)。

标签由TagItem类表示,封装了名称和频率。它们被添加到顺序不重要的集合中,以便在布局过程中进行排序。

调用该Arrange()方法后,可以将标签云渲染为Graphics代表屏幕、位图或打印机的 GDI+ 对象。该Draw()方法接受一个目标矩形,标签云将在其中自动缩放和居中。

为了便于交互,HitTest()还提供了一种方法来确定指定坐标处的标签。

观察

  • 具有最高频率的标签按预期占据云的中心,并且首先倾向于垂直堆叠(因为这符合“方形”的标准)。
  • 随后的标签占据周围的空间,并按预期从中心逐渐向外移动。
  • 较短和较低频率的标签有时会填补较大标签之间的空白。当标签之间的字体大小差异变得更加极端时,这种情况发生得更多。
  • 性能开始下降超过大约 128 个标签。当您考虑随着标签数量的增加而降低可读性时,这似乎是可以接受的。
  • 标签频率的分布影响布局算法的性能。当使用正态分布随机生成时,它的运行速度比更典型的分布(其中少数标签占主导地位并且有许多标签的频率非常低)慢。

表现

尽管该算法包含 3 个嵌套循环,但第二级中的角度数保持不变,因此性能为 O(n²)。这可以通过逐渐增加标签的数量和测量最内层循环的周期数来证明:

标签云 javascript 标签云的作用_标签云 javascript_02

进一步优化

  • 通过以随机(打乱)顺序尝试角度,标签可以更好地填充可用空间
  • 无需尝试所有 360 个方向,而是可以通过从 shuffled 集中选择前 90 个方向来获得类似的结果
  • 距离不是每次都增加 1,而是可以以与每个标签的大小相当的增量增加
  • 如果超出了当前的最佳距离,我们可以停止增加距离并尝试另一个角度
  • 如果放置标签不会导致标签云的总边界完全增加,我们可以跳过剩余的角度并放置下一个标签

示例用法

TagCloud cloud = new TagCloud();

// add some tags
cloud.Items.Add(new TagItem("orange", 2));
cloud.Items.Add(new TagItem("red", 4));
cloud.Items.Add(new TagItem("green", 12));
cloud.Items.Add(new TagItem("pink", 96));
cloud.Items.Add(new TagItem("black", 1));
cloud.Items.Add(new TagItem("brown", 50));
cloud.Items.Add(new TagItem("yellow", 45));
cloud.Items.Add(new TagItem("purple", 32));
cloud.Items.Add(new TagItem("gold", 8));
cloud.Items.Add(new TagItem("silver", 7));

// apply layout
cloud.Arrange();

using (Bitmap bmp = new Bitmap(512, 512)) {
   // render to bitmap
   cloud.DrawToBitmap(bmp);

   // save bitmap
   bmp.Save("test.png", ImageFormat.Png);
}

最后的想法

这种实现产生了良好的结果和相当好的性能。我看到了进一步优化的潜力(能够实现 O(n log n) 性能将是非常可取的!)和附加功能,例如能够在每个标签的基础上改变文本颜色和其他视觉属性。