尧图精选

唐朝诗人群像可视化:从数据建模到ECharts图谱的工程实践

🕒 发布时间:2026/9/17 17:09:23 📁 来源:尧图网络
简介这是一份面向计算机设计大赛参赛者与数字人文可视化初学者的完整项目源码包来自“诗意千年——唐朝诗人群像的数字展现”作品。项目整合诗人传记、诗歌文本与历史文献通过数字技术将唐朝诗人的生平与创作历程转化为可交互的视觉叙事适合作为文化传承类项目的参考样板也可用于学习数据可视化与前端交互实现。压缩包共109个文件总大小44.68MB文件类型覆盖完整前端工程81个PNG图片提供诗人画像、背景及词云图基础素材9个JS脚本处理动态渲染与交互逻辑6个HTML页面搭建多场景展示结构4个CSS文件完成视觉排版另有4个JSON存放结构化数据、4个JPG辅助图片及1个说明文档打开即可对照理解项目架构。当前已有160人学习浏览。获取源码后可复用其页面设计、词云图展示、样式主题与数据组织方式帮助快速搭建同类数字人文可视化项目。1. 数字人文项目的技术拆解唐朝诗人群像到底在做什么拿到“唐朝诗人群像的数字展现”这类计算机设计大赛作品多数人第一反应是去看界面炫不炫、动画多不多但真正决定作品上限的往往是看不见的数据层和交互链路。这个项目本质上是一个数字人文Digital Humanities可视化应用把诗人的生平、作品、社交关系、时代背景整理成结构化数据再用前端图表引擎渲染成可交互的关系图谱、时间轴和作品索引。它面向的受众是历史爱好者和评委但背后使用的技术栈和普通中后台系统没有本质区别。本文不讨论这个 zip 包本身是否完美而是顺着“数字展现”这条主线索把这类项目从选型、建模、渲染到交付的完整工程路径讲清楚。无论是准备参赛、做课程设计还是想在企业内部做文化类数据看板这套思路都能直接复用。2. 技术选型与数据建模把诗人、作品和关系变成可查询的 JSON2.1 可视化项目的技术栈选择与取舍做数字人文类项目常见的技术路线有三条第一条是纯前端方案用 Vue 或 React 配合 ECharts、D3.js 做全部渲染数据放在静态 JSON 或本地 SQLite 里优点是部署简单、适合比赛演示第二条是前后端分离用 Flask 或 Node.js 提供 API前端负责展示适合数据量大、需要动态筛选的场景第三条是基于 Notebook 的分析型方案用 Python 处理完数据后导出 HTML 快照交互性较弱但开发速度最快。对于“唐朝诗人群像”这种中等规模数据集我一般推荐第一种为主、第二种为辅。原因很直接诗人数量通常在一两百人以内诗歌几千首完全可以用静态 JSON 承载但为了支持“按时期筛选”“按关系跳转”这类交互纯静态方案需要在前端写大量过滤逻辑反而比加一个薄 API 层更麻烦。折中的做法是数据准备阶段用 Python 脚本清洗生成 JSON前端通过 fetch 加载本地起一个静态服务器调试最终打包时把数据内联进构建产物。技术选型的具体建议如下表模块推荐方案替代方案选型理由前端框架Vue 3 ViteReact Webpack模板语法直观选手上手快Vite 启动快图谱渲染ECharts 5D3.js / AntV G6内置力导向布局配置简单文档齐全数据格式JSON 索引文件SQLite APIJSON 可读性好便于提交源码时直接查看样式方案Tailwind CSS原生 CSS快速搭建图文混排页面2.2 诗人、作品与社交关系的数据模型设计数据建模是“数字展现”项目最容易翻车的环节。很多人一上来就写前端结果做到关系图谱时发现缺少“师徒关系”“同年出生”这类边信息被迫返工。为了避免这个问题我习惯在写任何 UI 代码之前先用 TypeScript 接口或 JSON Schema 把数据结构固定下来。诗人节点至少需要以下字段id唯一标识、name姓名、birth_year和death_year负数为公元前、period初唐/盛唐/中唐/晚唐、tags标签数组如“山水田园”“边塞”、poem_count存诗数量、bio精简人物小传、代表作列表。关系边则至少包含source、target、relation如“师徒”“好友”“同僚”和description。需要注意的是年份字段不要用字符串存比如“约712年”这种值无法参与时间轴计算。我建议拆成birth_year_approx和birth_year_uncertain两个字段前者存可计算的数值后者标记是否精确。这样做时间轴缩放和区间筛选时就不会被脏数据卡住。诗歌数据可以单独建一个集合每首诗关联一个author_id如果作品有多个版本用variant_of字段指向主版本。2.3 用 Python 脚本清洗和生成图谱 JSON数据清洗通常是这类项目里最耗时的一步。来源可能是古诗文网爬虫结果、公开的数据集或者手动录入的 Excel字段命名不统一、年份有缺失、人名有别名。我一般会写一个一次性脚本把原始数据读入 pandas DataFrame做标准化处理后输出三个文件poets.json、poems.json、relations.json。下面是一个清洗脚本的核心片段import pandas as pd import json # 原始数据: 每行一位诗人, 包含生卒年、字、号、派别 df pd.read_csv(raw_poets.csv) # 补全省略的公元纪年 df[birth_year] df[birth_year].fillna(0) # 时期划分: 按生年或主要活动年份归类 def assign_period(row): y row[birth_year] if y 650: return 初唐 elif y 713: return 盛唐 elif y 790: return 中唐 else: return 晚唐 df[period] df.apply(assign_period, axis1) # 输出精简字段 poets [] for _, r in df.iterrows(): poets.append({ id: r[id], name: r[name], birth_year: int(r[birth_year]), period: r[period], tags: [t.strip() for t in r[tags].split(|)], }) with open(public/data/poets.json, w, encodingutf-8) as f: json.dump(poets, f, ensure_asciiFalse, indent2)这段代码做的事情是把原始 CSV 里的诗人数据标准化成前端可直接消费的 JSON。fillna(0)的作用是处理“生年不详”的情况0 在时间轴上不会参与连线assign_period按生年粗分四唐实际项目中如果知道诗人的主要活动时期应该优先覆盖该字段。ensure_asciiFalse是关键参数不设置的话中文会被转成\uXXXX虽然浏览器能正常解析但提交源码后评委打开 JSON 文件会看到满屏转义字符体验很差。3. 关系图谱与时间轴唐朝诗人群像的核心界面实现3.1 用 ECharts 力导向图渲染诗人社交网络图谱是这类数字人文项目的主视觉。ECharts 的graph系列内置了力导向布局只需要提供nodes和links两个数组就能渲染出可拖拽的关系网。但在实际项目中直接扔全量数据会有两个问题第一节点数量超过 80 个时力导向布局会非常慢拖拽卡顿第二所有节点挤在一起标签互相遮挡视觉上完全不可读。所以要做一个分层处理默认只展示核心节点存诗数量前 40 位 它们之间的关系其余节点在用户点击“展开”时增量加载。下面给出一个最小可用的配置示例const chart echarts.init(document.getElementById(graph)); const option { tooltip: { formatter: (params) { if (params.dataType node) { const p params.data; return b${p.name}/bbr/${p.period}${p.birth_year || 生年不详}; } return params.data.relation; } }, series: [{ type: graph, layout: force, data: nodes, links: links, roam: true, draggable: true, label: { show: true, position: right, formatter: (p) p.data.name }, force: { repulsion: 150, edgeLength: 80, gravity: 0.1 }, lineStyle: { color: #aaa, width: 1, curveness: 0.2 } }] }; chart.setOption(option);这里layout: force开启力导向布局repulsion控制节点之间的斥力大小值越大节点越分散。edgeLength是理想边长太小会导致关系密集的节点重叠。curveness给边加一点弧度当两个诗人之间存在多条关系比如既是好友又是同僚时弧线能避免线条完全重合。roam: true允许用户缩放和平移画布这个是评委最常试的交互不要漏掉。3.2 时间轴联动按年代切片观察诗人群体的流动单纯的关系图谱只能回答“谁和谁认识”回答不了“盛唐时期诗人群体是如何聚拢的”这个问题。所以数字人文项目几乎都要配一条可拖拽的时间轴。实现思路是准备一批预计算好的“年代快照”每个快照包含截止到该年份存活的诗人列表以及他们之间的关系。拖动时间轴时动态更新chart.setOption的data和links。const slider document.getElementById(timeline); const yearLabel document.getElementById(year); slider.addEventListener(input, (e) { const year parseInt(e.target.value, 10); const snapshot snapshots.find((s) s.year year); if (snapshot) { chart.setOption({ series: [{ data: snapshot.nodes, links: snapshot.links }] }); yearLabel.textContent ${year} 年; } });快照数据可以在 Python 脚本里提前算好遍历诗人的出生年和去世年按年份过滤出“此时在世”的节点再筛选两端都在该集合内的边。这种方式比前端即时计算快得多因为前端只需要做一次数组替换CPU 开销极小。需要注意的是快照数组每次更新时应该调用chart.clear()再setOption否则旧的图例和状态可能残留。3.3 诗人卡片与作品索引的联动逻辑关系图谱解决了“宏观”视角还需要一个“微观”入口让用户了解个体诗人。常见做法是点击图谱中的节点右侧滑出一个详情面板展示诗人小传、代表作节选、存诗数量统计和关联人物列表。这里的核心技巧是事件委托——给整个画布绑定一个click事件通过params.dataType判断是否点中了节点而不是给每个节点单独绑定监听器否则节点增删时容易造成内存泄漏。作品索引可以用一个简单的搜索框实现支持按诗名、诗句和作者三个维度的模糊匹配。因为数据量不大几千首前端全量过滤完全可行不需要引入 Elasticsearch。用一个computed属性监听输入值过滤出匹配结果再点击结果时自动定位到对应作者节点并高亮其所有关系边。高亮方式的实现是重新遍历links把非目标边设置为浅色透明目标边加粗。4. 数据渲染与视觉叙事让“数字展现”不只是图表堆砌4.1 从工具思维到叙事思维一个页面里该有什么很多计算机设计大赛的作品在技术实现上没毛病但视觉效果看起来像“数据管理后台”左侧一个表格右侧一个图表底部再来一个折线图。问题出在缺少主次关系。围绕唐朝诗人群像这个主题首屏应该遵循“背景层—主体层—细节层”的三层叙事结构。背景层是一幅淡化的唐代疆域或山水画风格的页面底色主体层是居中偏左的关系图谱细节层是右侧的诗人详情卡片和底部的时间轴。用户第一眼看到的是图谱其次是时间轴暗示“可以拖动”最后才是详情卡片等待被点击。具体实现上头部用一张约 600ms 缓入的暗色渐变图作为视觉锚点标题字体选用偏书法的开源字体比如“站酷庆科黄油体”或“霞鹜文楷”但正文继续用系统字体保证可读性。字体文件要注意按unicode-range切片加载否则一个几 MB 的字体文件会拖慢首屏速度。这里可以使用font-display: swap确保文字先展示字体加载完成后替换。4.2 颜色系统与关系边样式如何影响信息密度唐朝诗人群体有明确的时代分区颜色系统应该辅助用户“一眼看懂分区”而不是仅仅为了好看。我建议四唐各用一种主色比如初唐用青灰#7f8c8d、盛唐用金棕#b8860b、中唐用靛蓝#3b5998、晚唐用赭红#a0522d。节点内部用对应颜色填充同时给节点加一圈同色系的半透明外发光增强视觉分量。关系边的颜色默认是灰色但不同类型的边可以用线型区分师徒关系用实线好友用虚线同僚用点线。ECharts 的lineStyle.type支持这三种线型配合tooltip中显示关系描述信息密度就出来了。这里需要注意一个细节当某个节点被选中时它的关联边应该保留颜色非关联边统一淡化为 20% 透明度的灰色。这个逻辑要用图层管理来做ECharts 同一 series 中无法针对单条边单独设置透明度并实时响应所以需要准备两份 links 数据一份是全量原始数据一份是“被选中时的过滤数据”切换时整体替换而不是逐条修改。4.3 动效时长与滚动叙事的工程实现数字人文项目普遍存在过度动效的问题。力导向图本身就是动态的如果加载时再叠加旋转、放大或者逐个弹出动画页面会显得很“晃”。我常用的策略是首次加载时只做一个 300ms 的节点透明度渐变力导向的“自然凝聚”过程本身就是最好的动画不需要额外编排。切换到时间轴快照时通过setOption替换数据时给animationDurationUpdate设置为 400ms形成节点平滑移位的效果。诗人详情卡片滑入时用 200ms 的transform: translateX配合ease-out曲线做到“快而不生硬”。滚动叙事一般用在首页下半部分用户滚动时依次看到四个时期的代表人物卡片每个卡片横跨一张时间轴小图。这里用 CSSposition: sticky做简单的滚动驱动式交互即可不必引入 GSAP 或 ScrollMagic 这类重量级库。如果确实想让视觉更有“诗卷感”可以考虑用 Canvas 绘制一条贯穿页面的丝线丝线路径由滚动位置映射到一条三次贝塞尔曲线这是性价比最高的定制动效。5. 源码落地与打包优化拿别人的 zip 也能跑起来的工程细节5.1 目录结构与源码包的可交付性比赛或课程设计提交的 zip 包评委第一步打开看到的应该是清晰的目录结构而不是一堆无规则文件。我一般会按下面这种方式组织源码包poetry-tang/ ├── index.html ├── package.json ├── vite.config.js ├── README.md ├── public/ │ ├── data/ │ │ ├── poets.json │ │ ├── poems.json │ │ ├── relations.json │ │ └── snapshots.json │ └── assets/ │ └── bg-tang.jpg └── src/ ├── main.js ├── App.vue ├── components/ │ ├── PoetGraph.vue │ ├── PoetDetail.vue │ ├── TimelineSlider.vue │ └── PoemSearch.vue ├── utils/ │ └── graphUtils.js └── styles/ └── main.cssREADME.md至少要有三块内容环境要求Node 版本、包管理器、启动命令、数据文件格式说明。公开数据集的来源和许可信息也要写明这是很多参赛作品忽略的合规点。5.2 一段可复用的数据完整性校验脚本zip 压缩包在拷贝、解压过程中最容易出现数据文件缺失或 JSON 截断的问题。以下是基于 Node.js 的完整校验脚本核心是用JSON.parse验证数据文件可解析性同时实现节点 ID 与关系引用的交叉验证。import fs from fs; import path from path; const dataDir path.resolve(public/data); function readJson(file) { const raw fs.readFileSync(path.join(dataDir, file), utf-8); return JSON.parse(raw); // 解析失败会抛错, 便于定位问题 } function validate() { const poets readJson(poets.json); const relations readJson(relations.json); const poetIds new Set(poets.map((p) p.id)); // 检查节点唯一性 if (poetIds.size ! poets.length) { console.error([FAIL] 存在重复的诗人 id); process.exit(1); } // 检查关系引用的指向 const invalidEdges relations.filter( (r) !poetIds.has(r.source) || !poetIds.has(r.target) ); if (invalidEdges.length 0) { console.error([FAIL] ${invalidEdges.length} 条关系引用了不存在的节点); process.exit(1); } console.log([PASS] 数据完整性校验通过); } validate();将脚本保存为validate-data.mjs运行node validate-data.mjs即可。这套检查逻辑可以及时拦下“无向关系重复记录”“悬空引用”两类高频数据问题。若数据文件较大可将readFileSync换成createReadStream流式解析避免内存峰值过高。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →