
1. 缘起一个小说迷的执念与一次技术邂逅作为一个骨灰级的网络小说爱好者尤其是《斗破苍穹》的忠实粉丝我脑子里常年盘旋着一个念头能不能把我理解的、那个由“斗气大陆”、“异火”、“炼药师”、“远古八族”构成的宏大世界用一种更直观、更系统的方式呈现出来我试过用思维导图但人物关系、势力分布、时间线、功法传承这些维度交织在一起一张平面的图很快就变得像一团乱麻。我也试过用Excel表格虽然能分门别类但缺乏视觉上的关联性总觉得少了点“世界感”。这个执念就像萧炎执着于寻找异火一样一直萦绕在我心头。直到最近我在技术社区里闲逛时频繁地看到“Seed Evolving”这个词。起初我以为是什么新的AI模型或者开发框架深入了解后才发现它更像是一种新的内容创作与组织范式或者说是一种“思维容器”。它的核心是“种子”你可以把任何想法、知识点、项目作为一颗种子种下然后这颗种子可以“进化”不断生长出新的分支、关联和形态。这瞬间击中了我——这不正是我需要的吗一个可以容纳复杂世界设定并让其动态生长、可视化的工具。而另一个高频出现的词是“Skill”。在Seed Evolving的语境里Skill不是指编程技能而是一种可复用的、模块化的“能力”或“转换器”。比如一个“Markdown转HTML”的Skill一个“数据可视化”的Skill或者一个“知识图谱构建”的Skill。你可以通过组合不同的Skill让一颗种子比如一个小说世界的核心设定进化成各种形态一篇结构化的文章、一个交互式的网页、甚至是一张可探索的关系图谱。这让我豁然开朗我需要的不是某个单一的软件而是一套方法论和工具链将脑海中的混沌想法通过“播种”和“技能装配”最终“进化”成一个具象化的成果。于是我决定以《斗破苍穹》为试验田用Seed Evolving的思路结合一些基础的Web技术HTML/CSS/JS亲手将那个波澜壮阔的斗气大陆“建造”出来。这个过程远不止是做一个简单的信息汇总页面而是一次对叙事结构、知识管理和可视化表达的深度实践。2. 世界观解构从线性叙事到多维知识图谱动手之前我必须先完成最核心的一步解构。把一部数百万字、时间跨度漫长、人物关系错综复杂的小说从线性的阅读体验拆解成结构化的数据。这是整个项目的“种子”也是最耗费心力的部分。如果种子本身是混乱的后续无论用什么Skill去进化都只会得到一片杂草。2.1 确立核心维度与实体类型我首先摒弃了按章节罗列事件的做法而是尝试抽象出几个核心维度这些维度将构成我知识图谱的骨架实体Entities这是图谱的节点。我为《斗破苍穹》定义了以下几类实体人物萧炎、药老、美杜莎女王、魂天帝等。每个人物需要包含基础属性如姓名、别名、势力归属和动态属性如实力等级变化轨迹。势力/组织萧家、云岚宗、迦南学院、魂殿、丹塔等。需要明确其性质家族、宗门、学院、联盟、主要成员、敌对或盟友关系。地点/地域乌坦城、塔戈尔大沙漠、迦南学院、中州、古界等。需要描述其地理特征、所属势力、以及发生过的关键事件。物品/资源异火净莲妖火、陨落心炎等、功法斗技焚诀、焰分噬浪尺等、丹药筑基灵液、破宗丹等。这是《斗破苍穹》世界观的特色需要详细记录其特性、排名、获得者及效果。事件三年之约、丹会、净莲妖火降世、双帝之战等。事件是连接其他实体的纽带需要明确时间点或相对时间顺序、参与方、结果与影响。2.2 定义关系与属性字段光有实体不够必须定义它们之间如何连接。我设计了几种核心关系类型归属关系人物 - 属于 - 势力萧炎属于萧家、迦南学院。人际关系人物 - 师徒/父子/伴侣/盟友/仇敌 - 人物药老-萧炎为师徒萧炎-萧薰儿为伴侣。拥有/使用关系人物 - 拥有/掌握 - 物品/功法萧炎拥有玄重尺掌握焚诀。发生地关系事件 - 发生于 - 地点三年之约发生于云岚宗。参与关系人物/势力 - 参与 - 事件魂殿参与覆灭萧家事件。因果/序列关系事件A - 导致/引发 - 事件B云岚宗追杀萧炎导致其逃离加玛帝国。对于每个实体和关系我都需要设计属性字段来填充细节。例如对于“人物-萧炎”属性可能包括初始实力斗之气三段最终实力斗帝关键转折点获得焚诀、吞噬第一种异火。这些属性数据将成为后续可视化时鼠标悬停显示的详细信息。实操心得从“感觉”到“字段”这个过程最反直觉的是你必须把很多模糊的“感觉”量化成具体的字段。比如“萧炎和药老关系很好”这需要拆解为“关系类型师徒”、“信任度极高”、“共同经历共同逃亡、共同炼药、共同对敌”等多个可记录的维度。一开始会觉得很繁琐但一旦建立起这套字段体系后续的数据录入和检索就会变得异常清晰高效。我建议先用一个简单的表格如Airtable或甚至Excel来模拟这套结构验证其是否能够覆盖你想表达的所有信息。2.3 数据收集与结构化录入这是体力活。我重新快速浏览了小说并借助一些已有的粉丝百科和论坛帖子按照上面定义的维度和关系开始一条条地录入数据。我使用了最简单的工具——一个多标签页的电子表格Google Sheets或腾讯文档。Sheet 1: 人物表列包括ID、姓名、别名、简介、出生势力、最终势力、实力成长轨迹JSON字符串或分列、关键物品、备注。Sheet 2: 势力表ID、名称、类型、首领、主要成员关联人物ID、敌对势力关联势力ID、盟友势力、所在地、简介。Sheet 3: 异火/功法/丹药表ID、名称、类型异火/功法/丹药、排名/等级、效果描述、获得者/创造者关联人物ID、相关事件。Sheet 4: 事件表ID、名称、发生时间相对章节或年份、地点关联地点ID、参与人物关联人物ID、涉及势力、结果、后续影响。Sheet 5: 关系表这是核心。列包括关系ID、起始实体类型、起始实体ID、关系类型、目标实体类型、目标实体ID、关系强度可选、描述。通过ID进行关联保证了数据的一致性和可扩展性。当所有数据录入完毕我的“斗破苍穹世界种子”就初步成型了——它是一套结构化的、机器可读的数据集合而不再是脑海里一团模糊的印象。3. 技能装配从数据种子到交互式网页有了结构化的数据种子下一步就是让它“进化”。根据Seed Evolving的理念我需要选择合适的“Skill”来加工这颗种子。我的目标是生成一个可交互的HTML网页图谱这需要一系列技能的组合。3.1 Skill 1数据转换与导出我的原始数据在电子表格里需要转换成Web前端更易处理的格式通常是JSON。这里可以手动处理但更高效的方式是写一个简单的脚本Python或Node.js这就是一个自定义的“数据导出Skill”。# 示例一个简化的Python数据转换脚本思路 import pandas as pd import json # 读取各个sheet characters_df pd.read_excel(斗破数据.xlsx, sheet_name人物) relationships_df pd.read_excel(斗破数据.xlsx, sheet_name关系) # ... 读取其他表 # 转换为前端需要的图谱数据结构 graph_data { nodes: [], links: [] } # 处理人物节点 for _, row in characters_df.iterrows(): node { id: row[ID], name: row[姓名], type: character, properties: { # 将其他属性放入properties对象 alias: row[别名], final_power: row[最终实力], # ... 其他属性 } } graph_data[nodes].append(node) # 处理势力节点 (类似方法) # ... # 处理关系链接 for _, row in relationships_df.iterrows(): link { source: row[起始实体ID], target: row[目标实体ID], type: row[关系类型], value: row.get(关系强度, 1) # 关系强度可能影响可视化中的连线粗细 } graph_data[links].append(link) # 保存为JSON文件 with open(doupo_graph_data.json, w, encodingutf-8) as f: json.dump(graph_data, f, ensure_asciiFalse, indent2)这个脚本就是一个“数据清洗与JSON转换Skill”。运行它我就得到了一个干净的doupo_graph_data.json文件这是可视化库可以直接“食用”的原料。3.2 Skill 2可视化引擎的选择与集成这是核心的“可视化Skill”。在Web端力导向图是展示复杂关系网络最常用的形式。D3.js功能最强大但学习曲线陡峭。对于这个项目我选择了vis-network这个库它基于D3但封装了更简单的API专门用于网络图可视化并且交互功能拖动、缩放、点击、悬停开箱即用。首先创建一个标准的HTML骨架并引入必要的库!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title斗破苍穹世界图谱 | Seed Evolving实践/title script srchttps://unpkg.com/vis-network/standalone/umd/vis-network.min.js/script link hrefhttps://unpkg.com/vis-network/styles/vis-network.min.css relstylesheet typetext/css / style /* 自定义样式放在这里 */ #mynetwork { width: 100%; height: 800px; border: 1px solid #ccc; background-color: #f9f9f9; } .controls { padding: 10px; background: #eee; margin-bottom: 10px; } .node-character { background-color: #6ab04c; } /* 人物节点颜色 */ .node-faction { background-color: #eb4d4b; } /* 势力节点颜色 */ .node-item { background-color: #f9ca24; } /* 物品节点颜色 */ .tooltip { ... } /* 自定义提示框样式 */ /style /head body div classcontrols h1斗破苍穹世界交互图谱/h1 button onclickfilterByType(character)只看人物/button button onclickfilterByType(faction)只看势力/button button onclickresetFilter()显示全部/button input typetext idsearchBox placeholder搜索人物或势力... onkeyupsearchNode() /div div idmynetwork/div div idnodeDetails/div !-- 用于显示节点详情的区域 -- script srcdoupo_graph_data.json/script !-- 引入我们生成的数据 -- script srcmain.js/script !-- 主要的可视化逻辑 -- /body /html3.3 Skill 3核心交互逻辑实现接下来是main.js文件这是驱动整个图谱的“大脑”也是一个关键的“交互逻辑Skill”。// main.js // 1. 定义节点和边的样式 var nodes new vis.DataSet(data.nodes); // data 来自 doupo_graph_data.json var edges new vis.DataSet(data.links); // 为不同类型节点设置样式 nodes.forEach(function(node) { switch(node.type) { case character: node.color {background: #6ab04c, border: #2d3436}; node.shape dot; node.size 20; break; case faction: node.color {background: #eb4d4b, border: #2d3436}; node.shape diamond; node.size 25; break; case item: node.color {background: #f9ca24, border: #2d3436}; node.shape triangle; node.size 18; break; // ... 其他类型 } // 将额外属性存到title用于悬停提示 node.title strong${node.name}/strongbr/ 类型${node.type}br/ ${node.properties ? Object.entries(node.properties).map(([k,v]) ${k}: ${v}).join(br/) : } ; }); // 2. 创建网络图 var container document.getElementById(mynetwork); var graphData { nodes: nodes, edges: edges }; var options { nodes: { font: { size: 14, face: Microsoft YaHei }, borderWidth: 2, }, edges: { arrows: { to: { enabled: true, scaleFactor: 0.5 } }, // 关系带箭头 color: { color: #7f8c8d, opacity: 0.6 }, width: 2, smooth: { type: continuous }, // 平滑曲线 }, physics: { // 力导向布局配置 enabled: true, stabilization: true, // 初始布局稳定化 barnesHut: { gravitationalConstant: -2000, centralGravity: 0.3, springLength: 200, avoidOverlap: 0.5 } }, interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true } }; var network new vis.Network(container, graphData, options); // 3. 交互事件处理 // 点击节点显示详细信息 network.on(click, function(params) { if (params.nodes.length 0) { var nodeId params.nodes[0]; var node nodes.get(nodeId); var detailHtml h2${node.name}/h2p类型${node.type}/p; if (node.properties) { detailHtml ul; for (var key in node.properties) { detailHtml listrong${key}:/strong ${node.properties[key]}/li; } detailHtml /ul; } // 查找与此节点相关的边 var connectedEdges edges.get({ filter: function(edge) { return edge.from nodeId || edge.to nodeId; } }); if (connectedEdges.length 0) { detailHtml h3关联关系/h3ul; connectedEdges.forEach(function(edge) { var otherNodeId edge.from nodeId ? edge.to : edge.from; var otherNode nodes.get(otherNodeId); detailHtml li${edge.type} - a href# onclickhighlightNode(${otherNodeId});return false;${otherNode.name}/a/li; }); detailHtml /ul; } document.getElementById(nodeDetails).innerHTML detailHtml; } }); // 4. 实现控制面板的过滤和搜索功能 function filterByType(type) { var filteredNodes nodes.get({ filter: function(node) { return node.type type; } }); var filteredEdges edges.get({ filter: function(edge) { var sourceNode nodes.get(edge.from); var targetNode nodes.get(edge.to); return sourceNode targetNode sourceNode.type type targetNode.type type; } }); network.setData({nodes: filteredNodes, edges: filteredEdges}); } function resetFilter() { network.setData({nodes: nodes, edges: edges}); } function searchNode() { var query document.getElementById(searchBox).value.toLowerCase(); if (!query) { nodes.forEach(function(node) { node.hidden false; node.color node.color; // 重置颜色 }); network.setData({nodes: nodes, edges: edges}); return; } nodes.forEach(function(node) { if (node.name.toLowerCase().includes(query)) { node.hidden false; node.color {background: #3498db, border: #2c3e50}; // 高亮颜色 } else { node.hidden true; } }); // 只显示与高亮节点相关的边 var visibleNodeIds nodes.getIds({filter: function(n) { return !n.hidden; }}); var visibleEdges edges.get({ filter: function(edge) { return visibleNodeIds.includes(edge.from) visibleNodeIds.includes(edge.to); } }); network.setData({nodes: nodes, edges: visibleEdges}); }避坑指南性能与数据规模当节点和边数量超过500个时vis-network在默认物理模拟下可能会变得卡顿。我的《斗破苍穹》图谱最终有300多个节点和近千条边遇到了这个问题。解决方案是优化数据合并一些次要节点如大量龙套角色可以聚合成“云岚宗弟子”这样一个节点。调整物理参数在options.physics中降低barnesHut.gravitationalConstant的绝对值使其负得少一些增加springLength可以减少节点间的斥力和引力让布局更快稳定。启用分层加载初始只加载核心人物和势力提供“展开更多”按钮按需加载次级节点和关系。使用stabilization务必启用physics.stabilization: true并可以设置stabilizationIterations来限制布局计算的迭代次数达到一个“基本可用”的布局后即停止以提升初始加载速度。4. 图谱的进化与扩展不止于静态关系一个基本的交互式图谱完成后Seed Evolving的“进化”思维让我不满足于此。这颗“种子”还能如何生长我尝试了以下几个方向的进化每个方向都可以看作加载了新的“Skill”。4.1 进化分支一时间线视图关系图谱是空间维度的而小说故事是时间维度的。我增加了一个时间线视图将“事件”实体按时间顺序排列。实现思路在HTML中增加一个div idtimeline。使用另一个库如vis-timeline与vis-network同源或者用简单的CSS和JS自己绘制。数据源仍然是同一个JSON但只筛选出type为event的节点并利用其发生时间属性进行排序。// 示例简化版时间线生成 function renderTimeline() { var events nodes.get({filter: function(n) { return n.type event; }}); events.sort((a,b) a.properties.relativeTime - b.properties.relativeTime); // 假设有相对时间属性 var timelineHtml div classtimeline-container; events.forEach(event { timelineHtml div classtimeline-item>