发表于2018 年 2 月 1 日
下载
该项目的源代码和二进制文件可在
https://github.com/BradSmith1985/TagClouds
典型的标签云标签云是一种显示主题/类别列表及其相对出现频率的机制。这对于在博客、新闻网站、讨论板、信息系统等上查找内容很有用。它们可以是文本的或图形的,尽管后者提供了更大的灵活性并且是本文的重点。
可以对标签云的布局进行很多思考。您会注意到此博客中使用的默认 WordPress 标签云只是按字母顺序列出标签,并使用逐渐变大的字体大小来表示更常用的标签。更复杂的布局尝试将最常用的标签放置在中心,最少使用的标签位于最远的位置。我什至为此目的探索了 力导向算法,尽管我后来得出结论认为它们不太适合这个问题。
我最终采用的解决方案采用迭代方法,并使用适应度标准来达到最佳布局。它将每个标签按从最高频率到最低频率的顺序放置在图表上。标签的位置仅由它之前的标签决定。从中心开始向外工作,选择一个位置,使标签不会与任何先前放置的标签发生碰撞。如果标签保持在该位置,则使用几个标准来计算解决方案的适应度。对所有方向(360 度)重复此过程。每次找到更优化的解决方案时,标签都会移动到该位置。当所有角度都用尽时,标签将处于最佳位置,并且控制移动到下一个标签。
健身标准如下:
- 标签云的总面积越小越好
- 标签云的平方*(即宽度和高度之间的差异最小)越多越好
- 离标签云中心的距离越小越好
* 或其他一些纵横比
算法
- 按频率降序对标签集合进行排序。
- 让 totalBounds表示标签云的使用边界,并初始化为零。
- 对于集合中的 每个标签:
- 让 bestBounds 表示迄今为止最优解的边界,并初始化为零。
- 让 bestDist表示与标签云中心的距离,以获得最佳解决方案,并将其初始化为零。
- 对于从 0 到 360 度 的每个角度:
- 让tagBounds表示 tag 的边界(位置和大小),并在标签云的中心初始化。
- 令tagDist表示距标签云中心的距离,并初始化为零。
- 重复以下操作:
- 将tagBounds从标签云中心以角度移动到一个点tagDist单位。
- 检查tagBounds是否与之前放置的任何标签的边界发生冲突(相交)。
- 如果有冲突,增加tagDist。否则,退出循环。
- 让 isBest表示以下是否全部为真:
- ( totalBounds ∪ tagBounds ) 的面积小于 bestBounds的面积(或者 bestBounds为空)
- ( totalBounds ∪ tagBounds )的宽高差小于bestBounds的宽 高差(或者 bestBounds为空)
- tagDist小于 bestDist(或 bestDist为零)
- 如果 isBest为真:
- 将 bestBounds设置为 ( totalBounds ∪ tagBounds )。
- 将 bestDist设置为 tagDist。
- 将 标签移动到 bestBounds的位置。
- 将 totalBounds设置为 bestBounds。
执行
我对上述算法的实现采用名为 的 C# 类的形式TagCloud
,它表示标签云并提供将布局应用到屏幕或位图的方法。
包括指定基本视觉属性的能力,例如字体、颜色和样式,以及具有最高和最低频率的标签字体大小之间的渐变。我还提供了一种支持特定纵横比的方法(因为不是每个人都想要一个完美的方形标签云),尽管应该注意最终标签云的纵横比将介于所需值和 1:1(方形)。
标签由TagItem
类表示,封装了名称和频率。它们被添加到顺序不重要的集合中,以便在布局过程中进行排序。
调用该Arrange()
方法后,可以将标签云渲染为Graphics
代表屏幕、位图或打印机的 GDI+ 对象。该Draw()
方法接受一个目标矩形,标签云将在其中自动缩放和居中。
为了便于交互,HitTest()
还提供了一种方法来确定指定坐标处的标签。
观察
- 具有最高频率的标签按预期占据云的中心,并且首先倾向于垂直堆叠(因为这符合“方形”的标准)。
- 随后的标签占据周围的空间,并按预期从中心逐渐向外移动。
- 较短和较低频率的标签有时会填补较大标签之间的空白。当标签之间的字体大小差异变得更加极端时,这种情况发生得更多。
- 性能开始下降超过大约 128 个标签。当您考虑随着标签数量的增加而降低可读性时,这似乎是可以接受的。
- 标签频率的分布影响布局算法的性能。当使用正态分布随机生成时,它的运行速度比更典型的分布(其中少数标签占主导地位并且有许多标签的频率非常低)慢。
表现
尽管该算法包含 3 个嵌套循环,但第二级中的角度数保持不变,因此性能为 O(n²)。这可以通过逐渐增加标签的数量和测量最内层循环的周期数来证明:
进一步优化
- 通过以随机(打乱)顺序尝试角度,标签可以更好地填充可用空间
- 无需尝试所有 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) 性能将是非常可取的!)和附加功能,例如能够在每个标签的基础上改变文本颜色和其他视觉属性。
标签:TagItem,更好,标签,Add,构建,Items,new,cloud From: https://www.cnblogs.com/firespeed/p/16709208.html