前言

我之前不是开发 StarBlogPublisher(一款 Markdown 文章发布工具)吗?

当时里面有个分类 词云(Word Cloud) 展示功能。

初版的词云虽然 "能用",但效果极其粗糙——基本只是简单堆叠文字,完全没有体现出词云那种灵动、密集、错落有致的美感。

于是,我决定 彻底重构 这一模块,重新寻找合适的词云生成方案。

选型

在 Avalonia 生态中是没有直接可用的词云组件的。

不过没事,C# 的生态还算丰富,基本要啥有啥,词云自然不在话下。

在调研阶段,我找到了两个比较流行的 C# 词云库:

简单对比一下:

特性 Sdcb.WordCloud KnowledgePicker.WordCloud
渲染引擎 SkiaSharp(跨平台) SkiaSharp(跨平台)
输出格式 图片(PNG)、SVG、JSON 图片(Bitmap)、SVG(需要自绘)
自定义程度 高(遮罩、字体、多方向、JSON输出等) 中(字体、颜色、布局可定制,但不支持遮罩)
遮罩功能 原生支持遮罩图生成特定形状词云 暂不支持遮罩,生成规则矩形词云
最近维护状态 活跃(2024年持续更新) 活跃(2024年有提交)
使用复杂度 中(配置多、自由度高) 中(较简洁,适合快速集成)

共同点

  • 两者都使用 SkiaSharp,意味着可以在 Windows、Linux、macOS 等多平台运行。
  • 都支持灵活配置字体、布局、颜色,并且速度非常快。

主要区别

  • Sdcb.WordCloud 更注重视觉效果(支持复杂遮罩图案),适合追求自定义形状、炫酷效果的场景。
  • KnowledgePicker.WordCloud 更注重性能和简洁性,适合标准矩形词云生成,不追求复杂形状。

最终,我选择了功能更强大、兼容性更好的 Sdcb.WordCloud

Sdcb.WordCloud简介

Sdcb.WordCloud 是一个基于 SkiaSharp 的跨平台词云生成库,具备以下特点:

  • 跨平台兼容:Windows、Linux、macOS 均可使用。
  • 多种输出:支持生成图片、SVG文件或JSON数据。
  • 高度可定制:自定义字体、颜色、遮罩图案、文本排列方式等。
  • 无依赖System.Drawing:在服务器环境也能轻松部署。
  • 开源友好:MIT License,开发者自由扩展。

安装

dotnet add package Sdcb.WordCloud

实战:在 StarBlogPublisher 中应用

重构后的词云生成逻辑主要分为两步:

获取词频数据

首先,从后端API请求分类词频数据,并进行简单扩充(让词云密度更高)。

private async Task<List<WordScore>?> GetWordScores() {
var response = await ApiService.Instance.Categories.GetWordCloud();
if (response.Data == null) throw new Exception("获取词云数据失败"); var originalScores = response.Data
.Select(e => new WordScore(Score: e.Value, Word: e.Name))
.ToList(); var extendedScores = new List<WordScore>();
foreach (var score in originalScores) {
for (int i = 0; i < 10; i++) {
extendedScores.Add(score);
}
} return extendedScores;
}

这里小技巧:

将原本每个单词的词频复制多次,可以有效提升词云的视觉密度和丰富度。

生成词云图像

拿到词频数据后,使用 WordCloud.Create() 创建词云对象,并通过遮罩图案和字体定制,生成最终的词云图片。

private async Task GenerateWordCloudImage() {
var wordScores = await GetWordScores();
if (wordScores == null || !wordScores.Any()) {
ErrorMessage = "没有可用的词云数据";
return;
} var wc = WordCloud.Create(new WordCloudOptions(900, 900, wordScores) {
FontManager = new FontManager([
SKTypeface.FromFamilyName("Times New Roman")
]),
Mask = MaskOptions.CreateWithForegroundColor(
SKBitmap.Decode(await new HttpClient().GetByteArrayAsync(
"https://io.starworks.cc:88/cv-public/2024/alice_mask.png"
)),
SKColors.White
)
}); using var skImage = wc.ToSKBitmap();
using var data = skImage.Encode(SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(data.ToArray());
WordCloudImage = new Bitmap(stream);
}

这里用了两点增强体验的小技巧:

  • 遮罩图:使用一张指定形状的透明图,词云可以呈现人物轮廓、LOGO形状等,极大提升美感。
  • 自定义字体:更换字体可以让整体风格更符合网站/应用的设计感。

效果展示

话说之前的效果能算词云吗??

修改前 修改后

小结

通过这次重构,我总结出几点经验:

  • 选对库很重要,跨平台、高扩展性是首要考虑。
  • 词云美观与否,关键在于密度遮罩形状字体风格的搭配。
  • 尽可能异步请求局部优化,避免UI卡顿。

如果你也在C#项目中需要集成词云功能,推荐试试Sdcb.WordCloud —— 简单高效,而且效果不错

一次小而美的重构:使用 C# 在 Avalonia 中生成真正好看的词云的更多相关文章

  1. AIDE支持实时错误检查、代码重构、代码智能导航、生成APK

    AIDE是一个Android Java集成开发环境,可以在Android系统内进行Android软件和游戏的开发.它不仅仅是一个编辑器,而是支持编写-编译-调试运行整个周期,开发人员可以在Androi ...

  2. 以.net core重构原有.net framework过程中的一些API变更记录(持续更新)

    1)Type.IsGenericType类似属性变更 以下是.net framework 4.5中Type抽象类中泛型类型的几个个属性,用于泛型类型的相关信息判断: 以下是.net core(nets ...

  3. NetCloud——一个网易云音乐评论抓取和分析的Python库

    在17的四月份,我曾经写了一篇关于网易云音乐爬虫的文章,还写了一篇关于评论数据可视化的文章.在这大半年的时间里,有时会有一些朋友给我发私信询问一些关于代码方面的问题.所以我最近抽空干脆将原来的代码整理 ...

  4. .NET重构—单元测试的代码重构

    阅读目录: 1.开篇介绍 2.单元测试.测试用例代码重复问题(大量使用重复的Mock对象及测试数据) 2.1.单元测试的继承体系(利用超类来减少Mock对象的使用) 2.1.1.公用的MOCK对象: ...

  5. 重构第26天 移除双重否定(Remove Double Negative)

    理解:”移除双重否定”是指把代码中的双重否定语句修改成简单的肯定语句,这样即让代码可读,同时也给维护带来了方便. 详解:避免双重否定重构本身非常容易实现,但我们却在太多的代码中见过因为双重否定降低了代 ...

  6. Eclipse 中的重构功能

    Eclipse 中的重构功能使其成为了一个现代的 Java 集成开发环境 (IDE),而不再是一个普通的文本编辑器.使用重构,您可以轻松更改您的代码,而不必担心对别处造成破坏.有了重构,您可以只关注于 ...

  7. C# 利用范型与扩展方法重构代码

    在一些C#代码中常常可以看到 //An Simple Example By Ray Linn class CarCollection :ICollection { IList list; public ...

  8. eclipse重构详解(转)

    重构是对软件内部结构的一种调整,目的是在不改变软件行为的前提下,提高其可理解性,降低其修改成本.开发人员可以使用一系列重构准则,在不改变软件行为的前提下,调整软件的结构. 有很多种原因,开发人员应该重 ...

  9. .NET重构—单元测试重构

    .NET重构—单元测试重构 阅读目录: 1.开篇介绍 2.单元测试.测试用例代码重复问题(大量使用重复的Mock对象及测试数据) 2.1.单元测试的继承体系(利用超类来减少Mock对象的使用) 2.1 ...

  10. lua中 table 元表中元方法的重构实现

    转载请标明出处http://www.cnblogs.com/zblade/ lua作为游戏的热更新首选的脚本,其优势不再过多的赘述.今天,我主要写一下如何重写lua中的元方法,通过自己的重写来实现对l ...

随机推荐

  1. 6.App.vue配置

    1.修改<div id="app">指定动态路由,可以设置导航栏 <div id="app"> <!-- 导航栏 --> & ...

  2. Netty高级使用与源码详解

    粘包与半包 粘包现象 粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息. 服务端代码 public class HelloWorldSe ...

  3. DevExpress MVVM Framework. Interaction of ViewModels. Messenger

    学习记录: 学习地址:https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-i ...

  4. 创建Graphics对象的三种方法

    参考链接:https://www.cnblogs.com/wax01/p/4982691.html 方法一.利用控件或窗体的Paint事件中的PainEventArgs 在窗体或控件的Paint事件中 ...

  5. [BZOJ3160] 万径人踪灭 题解

    首先正难则反,想到答案即为满足第一条要求的回文子序列数量,减去回文子串数量.回文子串数量 \(hash+\) 二分即可,考虑前半部分. 假如我们将一个回文子序列一层层剥开,就会发现它其实是由多个相同的 ...

  6. 【软件开发】Git 概念与常用命令

    [软件开发]Git 概念与常用命令 Git 概念 存储方式 Git 是分布式存储,每一个 clone 下来的仓库都可以看成独立的个体,只是 Git 有提供同步功能,因此 Git 支持离线使用,因为本质 ...

  7. Flink学习(十七) Emitting to Side Outputs(侧输出)

    我们在生产实践中经常会遇到这样的场景,需把输入源按照需要进行拆分,比如我期望把订单流按照金额大小进行拆分,或者把用户访问日志按照访问者的地理位置进行拆分等.面对这样的需求该如何操作呢? 大部分的Dat ...

  8. Java字节码增强实际应用在哪些方面?

    Java字节码增强由于与业务应用耦合性较低,且可任意修改程序代码,所以在许多方面都有应用.也是许多公司产品实现的基础.下面大概分类一下: 1.在可观测和监控方面的应用 如果一个应用的架构服务之间的依赖 ...

  9. python实现监控站点目录,记录每天更新内容,并写入操作日志,以便查找病毒恶意修改

    问题描述:站点需要追溯代码的修改时间,以便尽早发现病毒恶意修改迹象,及时处理 运行环境:linux服务器,宝塔面板 示例代码:一.读取txt的文件路径,依次遍历所有目录下面的文件,并记录文件信息 pa ...

  10. Java 浮点型去除后面多余的零

    当我们输出的小数不知道有几位小数,也不知道后面有没有带零,去掉后面多余零可以采用以下方法.在实际使用中,多用于小数转百分数,百分数前面的小数乘以100后转String输出,输出的String很多带零, ...