文章关系图谱页面
我想给 3-hexo 增加一个独立的“文章星图”页面。它不是把所有文章平铺成标签云,而是把文章当作星点:相近的文章自然聚集,跨主题文章通过细线连接,读者可以在图中漫游并进入具体文章。
一句话先说结论:关系数据在 Hexo 构建期生成,页面只读取一份静态 JSON;浏览器端使用 Canvas 渲染力导向星图。
这样做不需要后端,也不需要在访问页面时扫描文章全文,部署到 GitHub Pages 后仍然可用。
目标和边界
这个页面解决的是“这篇文章附近还有什么”,而不是替代首页、分类页或全文搜索。
- 入口固定为
/article-graph/,导航菜单增加“星图”。 - 每篇已发布且未隐藏的文章是一个节点;草稿、
hidden: true文章不进入数据集。 - 标签、分类和手工指定的关联文章构成边;关系必须可解释,不能做黑箱相似度推荐。
- 单击节点进入文章,悬停和选中时显示摘要、标签、分类、发布日期和关联理由。
- 首版目标是流畅浏览几百篇文章;不做登录、收藏、评论和服务端推荐。
关系如何计算
节点只保留页面必需的数据,避免把正文全部塞进 JSON:
{
"id": "2026/01/29/Web/03-任务执行架构/",
"title": "03-任务执行架构",
"path": "/2026/01/29/Web/03-任务执行架构/",
"date": "2026-01-29",
"categories": ["Web", "长链路耗时任务怎么处理"],
"tags": ["web", "long-task"],
"summary": "API、队列、Worker 和状态存储的职责分层。",
"relations": ["2026/01/28/Web/02-消息队列/"]
}
边按下面的规则生成,权重越高,连线越清晰、力导向布局中的吸引力越强:
| 关系 | 权重 | 说明 |
|---|---|---|
手工 relations |
6 | 系列上下篇、前置知识、明确推荐阅读 |
| 相同二级分类 | 4 | 例如同属一个专题目录 |
| 相同一级分类 | 2 | 主题接近,但不应把整片图拉成一团 |
| 每个共同标签 | 1 | 最多累加到 3,避免热门标签制造过多边 |
只保留权重不低于 2 的边;每个节点最多展示权重最高的 8 条边。这样既能看见主题团簇,也不会出现一张无法阅读的蜘蛛网。
手工关系使用文章 front matter 中的相对 permalink 或文章 id,例如:
relations:
- 2026/01/28/Web/02-消息队列/
- 2026/01/30/Web/04-任务编排/
第一版可以不回填历史文章,先凭分类和标签自动成图;之后只为系列文章、跨领域但确实值得串联的文章补 relations。
页面体验
页面采用一整块星图画布,不把主体塞进大卡片里。桌面端右侧保留窄信息栏,移动端改为底部抽屉。
星图行为
- 节点:默认是低亮度象牙白点;分类决定细描边颜色,文章字数或关联度决定半径,置顶文章不额外放大。
- 连线:默认只显示低透明度细线;悬停、搜索或选中节点时,只高亮它的一跳关系。
- 聚类:布局初始按一级分类分区,随后以力导向计算微调;同类文章像星团,但不强行画封闭区域。
- 漫游:支持拖拽平移、滚轮或双指缩放、双击复位。视口外节点不绘制标题,缩小时只画点。
- 定位:搜索标题、标签或分类后自动聚焦第一个结果;列表显示匹配项,避免只靠在画布里找小点。
- 降级:启用
prefers-reduced-motion时关闭持续模拟;不支持 Canvas 或脚本加载失败时显示按分类排列的文章列表和普通链接。
信息与筛选
顶部只保留三个高频控件:搜索、分类筛选、重置视图。筛选项使用复选框而不是彩色文字按钮,颜色只作为辅助信息。
选中节点后,信息栏展示:
- 文章标题和发布日期
- 分类、标签
- 文章摘要
- 关联文章列表,以及“同标签”“同分类”或“手工关联”的关系说明
- 一个明确的“阅读文章”链接
键盘操作至少支持 Tab 进入控件、Enter 打开当前结果、Esc 清除选中状态。星图本身要提供一个可切换的列表视图,保证触摸屏和读屏软件都有可用路径。
NASA 灵感的视觉规范
这里借鉴的是航天任务的科学可视化语言,不使用 NASA 的 Logo、徽章或官方字体。
| 元素 | 建议 |
|---|---|
| 背景 | #07131F,叠加极弱的点阵和坐标网格 |
| 主要文字 | #F3EFE6,保证深色背景上的阅读对比度 |
| 主强调色 | #6EC8D6,用于焦点、链接和主分类 |
| 次强调色 | #C8D9EA,用于次级分类和辅助刻度 |
| 选中状态 | #F0A24A,只用于当前节点、结果计数和必要提示 |
| 字体 | Space Mono 用于数字/坐标,Noto Sans SC 或现有中文无衬线字体用于正文 |
| 形状 | 直角或 2px 小圆角、1px 细线、充足留白;不使用玻璃拟态、大圆角面板和霓虹光晕 |
背景网格、坐标、节点编号和细分割线需要克制到不影响文章标题阅读。节点文字遵循“按缩放级别渐进显示”:远看是星点,中距离显示标题,聚焦时才展示完整信息。
实现拆分
1. 构建期数据生成
新增 scripts/article-graph.js,注册 Hexo generator:
- 遍历
hexo.locals.get('posts'),过滤草稿和隐藏文章。 - 提取
title、path、date、categories、tags、excerpt和可选relations。 - 规范化 ID、去重、生成加权边并裁剪低价值连接。
- 输出
/article-graph/data.json,同时生成/article-graph/对应的页面数据。
摘要优先使用 Hexo 已生成的 excerpt;没有摘要时只截取纯文本前约 120 个字符。生成器不得把 HTML 正文直接输出,避免数据文件膨胀和未过滤标签混入画布。
2. 页面与静态资源
新增一个独立的 EJS 页面布局,例如 themes/3-hexo/layout/article-graph.ejs,复用当前主题的 header、侧边导航和 footer。页面只放必要骨架:canvas、筛选区、无障碍列表、详情区域和加载失败提示。
页面脚本和样式放在主题资源目录,例如:
themes/3-hexo/source/js/article-graph.js
themes/3-hexo/source/css/_partial/article-graph.styl
图布局使用本地打包的 d3-force,而不是访问外部 CDN。Canvas 负责节点与连线绘制,HTML 只负责输入控件、提示和详情内容;这样在节点增多时比 SVG 更稳定,也更适合缩放浏览。
3. 入口与配置
在 _config.3-hexo.yml 增加专用配置,所有可调项集中在这里:
article_graph:
on: true
path: article-graph
max_edges_per_node: 8
min_edge_weight: 2
show_hidden: false
再在主题的 menu.menus 增加“星图”入口,采用站内跳转。数据生成开关、边阈值和页面路径都不要写死在脚本中,后续调密度时只改配置即可。
实施顺序
- 先完成生成器,并在本地检查
data.json的节点数、边数、重复 ID 和无效路径。 - 再实现静态页面骨架、主题样式和列表降级视图,先保证没有 JavaScript 也能打开文章。
- 接入 Canvas 与
d3-force,实现初始布局、缩放、拖拽、悬停和选中。 - 增加搜索、分类筛选、键盘焦点、移动端抽屉和减少动态效果支持。
- 给 2 到 3 个系列文章补
relations,观察自动关系和手工关系是否符合阅读路径。 - 用真实文章规模调整边阈值、节点半径和文字显示规则,再决定是否需要加入年份筛选。
验收标准
npm run build成功,生成/article-graph/index.html和/article-graph/data.json。data.json中只包含公开文章;每个节点 ID 和path唯一,所有边两端都能找到节点。- 桌面端能搜索、缩放、拖拽、选中并跳转到文章;筛选不会重新请求页面。
- 移动端可缩放和查看节点详情,控制区与标题不重叠。
- 键盘可完成搜索、查看结果、清除选中和打开文章;关闭 JavaScript 时仍有分类文章列表。
- 在当前文章规模下连续操作没有明显掉帧,初始布局完成后不持续消耗 CPU。
需要后续确认的点
首版不建议自动分析正文语义。它需要额外的分词、向量模型或构建时间,产出的关系也不如标签和手工系列关系容易解释。
等基础星图稳定后,再评估两个增强项:按年份播放文章演进,以及用离线向量相似度补充“可能相关”的弱连接。后者必须明确标注为“语义相近”,不能和作者手工关联混在一起。