从零搭建一个初中知识图谱:原理、数据、存储与可视化实操教程
孩子说“这道题我不会”的时候,大多数家长和老师的第一反应是:再找十道同类题让他练。但很多时候,他不会的并不是这道题——而是三个月前的某个更基础的点。知识图谱解决的正是这个问题:它把按册、按章排下来的直线,换成一张有方向的网,让“不会”可以被追溯到具体的那一环。
这篇教程面向初中老师、家长,以及想自己动手做教具的开发者。我们以初中数学为例,从头走一遍完整流程:拆解两个成熟开源项目的做法 → 设计节点与边 → 数据从哪来 → 存在哪里 → 怎么可视化 → 怎么用在教学里 → 版权怎么算 → 30 天落地路线。文中所有项目规模、Star 数、许可信息都取自 GitHub 官方接口与项目 README 原文,核实时间为 2026-09-13。
先说结论:什么时候真的需要一张知识图谱
普通教材和刷题 App 给你的是一条直线:按册、按章、按顺序往下排。但真实的“不会”从来不是直线问题——学生做错“二次函数最值”,往往不是最值那一步没听懂,而是三个月前“配方法”没练熟,甚至一年前“整式乘法”就没过关。
知识图谱由三样东西构成:
- 节点(Node):一个“微主题”,足够小、能单独教、能单独测。比如“完全平方公式”“配方法”,而不是“整式”这种大章。
- 边(Edge):前置依赖,从“前置”指向“后续”,并标注强度(硬前置 / 软前置)和一句“为什么”。
- 属性(Props):掌握它算不算学会了(掌握证据)、怎么一句话当场问出来、对应教材哪一册。这些让图能用于教学,而不只是个漂亮摆件。
一句话判断你要不要做:如果你的用途是“找到卡点、规划补课路径、做期末知识体检”——需要做;如果只是想按顺序刷题——不需要,收益不高。
一、两个标杆项目:一个给数据契约,一个给中国底稿
同一条路上有两个值得研究的开源项目:一个来自美国团队、面向英美小学课程,开源了整套数据规范;一个由国内开发者做,面向中国 1–9 年级数学。搞清楚它们各自是什么,你就能决定自己从哪起步。
1.1 Marble Skill Taxonomy(withmarbleapp/os-taxonomy)——数据契约的范本
这是美国教育团队 Marble(公司主体 Generative Spark, Inc.)开源的一套“孩子到底要学什么”的知识图谱。它把小学阶段的知识拆成微主题,再用前置依赖连成有向无环图,并对齐多国课程标准。截至 2026-09-13,GitHub Star 为 4,351,许可为 ODbL 1.0(数据库层)。
它的核心规模:1,590 个微主题、3,221 条前置依赖、8 个学科。学科分布如下——这也是它偏小学的证据,数学体量远小于科学和英语:
| 学科 | 主题数 |
|---|---|
| Science 科学 | 547 |
| Mathematics 数学 | 503 |
| English 英语 | 286 |
| History 历史 | 90 |
| Personal & Social Development 个人与社会发展 | 88 |
| Life Skills 生活技能 | 37 |
| Computing 计算 | 21 |
| Learning to Learn 学会学习 | 18 |
它最有价值的地方,是把一个知识点该带什么字段定义得很清楚。看它的一个真实节点(原样引用自官方 README):
{
"id": "mt_N8CpN1EJrP",
"type": "CONCEPTUAL",
"subject": "English",
"domain": "Grammar & Punctuation",
"name": "Building sentences",
"description": "Understand that words combine to make sentences ...",
"ageRangeStart": 4,
"ageRangeEnd": 6,
"centrality": 0.257,
"evidence": [
"Distinguish between complete sentences and fragments",
"Compose a complete sentence with a subject and verb"
],
"assessmentPrompt": "If {{name}} says something like \"The dog\", can they tell you that's not a complete sentence?",
"standards": ["ccss-ela:L.K.1f", "uk-nc-2013:Eng.App2.Y1.Sent.1"]
}
对应的前置边长这样:
{ "topicId": "mt__00ZSLnB7p", "prerequisiteId": "mt_VBl1T1sFCM",
"strength": "hard",
"reason": "Must understand vibrations make sound before finding volume patterns" }
读法:topicId 依赖 prerequisiteId。把箭头方向反过来,就得到“学会它能解锁什么”。整套数据是纯 JSON,没有运行时、没有依赖,加载就能用。
许可(第 7 章展开):数据库层用 ODbL 1.0,文字内容层用 CC BY-SA 4.0。两者都是“署名 + 相同方式共享”,但 ODbL 明确区分了“衍生数据库”和“你用它做出的产品”——你用它在自己的产品里,产品可以闭源。
对你的直接可用性:它的数学只有 503 个点,且面向英美小学课程(年龄 4–12 岁区间偏多)。初中(7–9 年级)的内容它覆盖不足,也不能直接套中国课标。它的价值不在数据本身,而在那套 schema 契约 + 建图方法——你可以照它的格式,填中国初中的内容。
1.2 cn-k12-math-knowledge-graph ——现成的中国底稿
这是国内开发者 haojing8312 做的一个独立项目,2026 年 9 月 5 日创建,面向中国义务教育 1–9 年级数学,包含开源先修知识图谱 + 一个单文件的交互式 3D 浏览器。它明确说明自己是独立实现,只参考了 Marble 的公开 JSON 契约——这正好印证了上面那句:契约可以学,数据要自建。截至 2026-09-13,GitHub Star 为 35。
它的数据是四级结构,这点和 Marble 的扁平结构不同,非常贴近中国教材的组织方式:
| 层级 | 数量 | ID 示例 | 说明 |
|---|---|---|---|
| 册 book | 18 | math_1a_rjb |
一年级上册 … 九年级下册 |
| 章 chapter | 132 | math_1a_rjb_ch1 |
教材里的“章” |
| 星团 cluster | 196 | math_1a_rjb_ch1 |
章下面再分的学习簇 |
| 主题 topic | 1,327 | math_1a_rjb_cpt1 |
最小的可教可测点 |
边的结构也不止一种,分四个层级各有一套聚合边,真正的知识依赖在 edges.topic 里:
{
"edgeId": "k12edge-v2-00d20102ce6e049dbfb340f72bedf249",
"from": "math_2a_rjb_cpt15", // 前置知识
"to": "math_3b_rjb_skl10", // 后续知识
"kind": "prerequisite",
"sourceKind": "reviewed-candidate",
"strength": "hard",
"traceId": "sha256:153942bf..."
}
它的 1,204 条主题先修边来源分得很清楚,这点值得学,是数据可信度标注的范本:
- 609 条来自上游数据集 K12-KGraph(Hugging Face 上的开放数据集);
- 420 条是此前审核阶段补强的边;
- 175 条是 AI 数学审核补强的边。
作者特意写明:“AI 审核不会被描述为人工审核。”并且对 596 个“零入度”的未决主题做了全量复核——158 个补齐了前置,剩下 438 个保守地标记为“未决”,而不是硬说“确认没有前置”。这种诚实,是你在自建数据时最该抄的一课。
技术形态:整个可视化就是一个 index.html,853 KB,完全离线——用现代浏览器直接打开就行,不需要服务器、构建工具、也不发网络请求。3D 用 Three.js/WebGL,布局是一份预先算好的“倒圆锥”坐标(data/layout.json),所以打开即固定、不会每次都不一样。图是有向无环图(DAG),方向是“前置 → 后续”。
许可:数据与衍生可视化 CC BY-NC-SA 4.0(注意 NC = 非商业),查看器代码 Apache 2.0。它的数据是 NC 的,课程源自 K12-KGraph,要求署名、非商业、相同方式共享。你自己教学、校内使用没问题;拿它去做商业产品就要小心。这是它和 Marble 最大的许可差异。
1.3 两者怎么选:给你的决策表
| 维度 | Marble os-taxonomy | cn-k12-math |
|---|---|---|
| 覆盖 | 英美小学 8 学科,1,590 点 | 中国 1–9 年级数学,1,327 点 |
| 初中学段 | 基本没有 | 有(7–9 年级数学) |
| 结构 | 扁平:主题 + 依赖 + 课标对齐 | 四级:册 / 章 / 星团 / 主题 |
| 字段丰富度 | 高(含 evidence、assessmentPrompt) | 中(节点较精简) |
| 可视化 | 官网 3D 交互图 | 单文件离线 3D,可下载 |
| 数据许可 | ODbL 1.0(可商用) | CC BY-NC-SA 4.0(非商业) |
| 最适合拿来做什么 | 抄它的 schema 和字段设计 | 抄它的初中数学底稿和离线渲染思路 |
结论:做初中图,用 Marble 的字段契约当骨架,用 cn-k12-math 的初中数学数据当起点,两者组合。但因为它俩都是别人审过的“结果”,你要做成真正好用的教具,还得自己过一遍——第 3 章讲怎么补。
二、定骨架:节点和边到底怎么设计
动手前先把“本体(Ontology)”想清楚。这一步做对了,后面填数据就是体力活;做错了,填到一半你会发现图根本连不起来。
2.1 节点:切多细才合适
判断标准只有一条:这个点能不能单独出题检验、能不能单独讲 10 分钟。
| 太粗 ✗ | 合适 ✓ | 太细 ✗ |
|---|---|---|
| 整式 | 完全平方公式 | 完全平方公式里 a、b 的符号判断 |
| 二次函数 | 二次函数的最值 | 配方法第一步“提二次项系数” |
| 三角形 | 相似三角形的判定(AA) | AA 判定的一个具体例题 |
经验值:一册教材 60–120 个主题是舒服的粒度。cn-k12-math 的 1,327 个点分到 18 册,平均每册约 74 个,和这个经验值吻合。
2.2 给节点定字段(照 Marble 契约,按初中场景裁剪)
下面这套是建议的最小可用字段集,前几项必填,后面的可选:
{
"id": "cz_math_quad_vertex", // 稳定唯一 ID,全图引用它
"name": "二次函数的最值", // 显示名
"subject": "数学",
"domain": "函数", // 领域,用来上色分组
"grade": 9, // 年级(初中场景比年龄区间更实用)
"semester": "a", // 学期 a/b
"type": "PROCEDURAL", // CONCEPTUAL/PROCEDURAL/REPRESENTATIONAL
// ---- 可选但强烈建议 ----
"description": "能把一般式化成顶点式,读出顶点坐标,从而求最大/最小值。",
"evidence": [ // 掌握证据:能独立做到什么
"把 y=x²-4x+5 配方成 y=(x-2)²+1 并说出最小值",
"在区间限制下判断最值取在顶点还是端点"
],
"assessmentPrompt": "如果 {name} 拿到 y=x²-6x+5,他能不能先配方再告诉你在哪里取到最小值?",
"textbook": "人教版 九年级上册 22.1",
"difficulty": 3, // 1–5
"centrality": 0.42 // 中心度,越大人越关键(可脚本算)
}
2.3 给边定字段
{
"from": "cz_math_completing_square", // 前置:配方法
"to": "cz_math_quad_vertex", // 后续:二次函数的最值
"strength": "hard", // hard=不会这个就学不下去 / soft=有帮助
"reason": "求最值的标准方法就是配方,这一步不会则整个方法链断掉" // 一句话说清"为什么"
}
三个必须守的规矩:
- 必须是有向无环图(DAG)。A 依赖 B、B 又依赖 A 是死循环,学生会卡死。建完一定要跑校验脚本查环。
- 每条边都要写 reason。这是区分“知识图谱”和“随机连线图”的关键——说不出为什么连,就别连。
- hard 和 soft 要严格区分。hard 是“不学 A 就学不了 B”,soft 是“学了 A 学 B 更轻松”。这直接影响诊断时要不要强制回溯。
2.4 一个真实的初中例子:把“二次函数最值”往回追
这是你要建的那张图里最关键的一条链。顺着它,你就明白图为什么有用:
二次函数的最值
├─(hard)─ 配方法
│ ├─(hard)─ 完全平方公式
│ └─(hard)─ 整式乘法 ── 整式加减
├─(hard)─ 二次函数的图象与性质
│ ├─(hard)─ 二次函数
│ │ ├─(hard)─ 一元二次方程 ... 一元一次方程 ... 等式性质
│ │ └─(hard)─ 一次函数 ... 函数的概念 ... 平面直角坐标系
│ └─(soft)─ 一元二次方程根的判别式
└─(soft)─ 因式分解
当学生“最值”总错,你可以按这张图逐层往下点亮 / 熄灭节点,找到第一个熄掉的环——那才是真正要补的地方。在错误的地方反复刷题,是效率最低的做法。
三、数据从哪来:三条路,按成本排序
技术从来不是难点,数据才是。这一章解决“初中知识点和前置关系怎么搞到手”。
- 路线 A · 最省力|复用现成开源数据:下载
cn-k12-math的graph.json,剪出 7–9 年级那部分,补齐字段即可。适合想快速看到成品的人。 - 路线 B · 推荐|从教材目录半自动构建:拿教材目录拆微主题,用 AI 抽前置关系,人工审核。适合初中老师做自己学科的图。
- 路线 C · 个性化|从错题反向构建:从学生实际错题出发,倒推涉及的节点,逐步补全。适合给单个孩子做诊断图。
3.1 路线 B 的五步流水线
- 取目录:把目标教材的目录(章节 + 小节标题)整理成纯文本,一行一条。
- 拆微主题:让 AI 把每个小节拆成 1–4 个微主题,输出
name / grade / domain / type。这一步只需教材目录,不涉及版权问题。 - 抽前置关系:把全部主题列表丢给 AI,让它只输出
from / to / strength / reason四元组。提示词见附录。 - 人工审核:逐条过一遍。重点查三类——明显该连没连的、连反了方向的、reason 说不通的。这一步不能省。
- 跑校验:查重 ID、查悬空引用、查环(DAG)。附录给了脚本。
AI 抽关系的提示词要点:关键是限定它只能输出四元组、并强制给出理由,否则它会给你一堆没法用的解释文字。另外要明确告诉它“宁缺毋滥”——宁可少连,不要瞎连。
3.2 数据质量:学 cn-k12-math 的标注法
给每条边标上来源,这既是诚实,也是你日后排查的线索:
| 来源标记 | 含义 | 可信度 |
|---|---|---|
source |
来自权威数据集 / 课标 | 高 |
human-reviewed |
人(老师)逐条审核过 | 最高 |
ai-reviewed |
AI 抽取 + AI 复核,尚未经人 | 中,需复核 |
pending |
来回推敲后仍不确定 | 低,保留但不启用 |
红线:不要把 AI 审核的边说成“人工审核”。学生用这张图找卡点,一条错边可能把他引到错误的复习方向。宁可标 pending 也不要瞎连。
四、存储与查询:按规模选,别一上来就上重型武器
很多人一听说知识图谱就想到 Neo4j。对初中场景,这是杀鸡用牛刀。
4.1 起步:单个 JSON 文件(少于 2000 个节点都够用)
cn-k12-math 和 Marble 都是这么做的——纯 JSON,没有数据库。优点是能直接塞进单文件 HTML、离线跑、方便 git 管理。
{
"version": "1.0",
"subject": "初中数学",
"topics": [
{ "id": "...", "name": "...", "grade": 9, "domain": "函数", "type": "PROCEDURAL",
"description": "...", "evidence": ["..."], "assessmentPrompt": "..." }
],
"edges": [
{ "from": "...", "to": "...", "strength": "hard", "reason": "..." }
]
}
4.2 进阶:SQLite(想带进课堂、要能改状态时)
如果你要给每个学生记录“哪些节点已掌握”,就需要写入——这时 SQLite 最合适:单文件、免安装、可以跟课件一起拷走。
-- 知识点表
CREATE TABLE topics (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
subject TEXT, domain TEXT, grade INTEGER,
type TEXT, description TEXT, evidence TEXT
);
-- 前置依赖表
CREATE TABLE edges (
from_id TEXT NOT NULL REFERENCES topics(id),
to_id TEXT NOT NULL REFERENCES topics(id),
strength TEXT CHECK(strength IN ('hard','soft')),
reason TEXT,
PRIMARY KEY (from_id, to_id)
);
-- 学生的掌握状态(每个孩子一张表或加 student_id 列)
CREATE TABLE mastery (
student_id TEXT, topic_id TEXT,
state TEXT CHECK(state IN ('known','shaky','risk','unknown')),
updated_at TEXT,
PRIMARY KEY (student_id, topic_id)
);
-- 查"某个知识点的全部直接前置"
SELECT t.name FROM edges e JOIN topics t ON t.id = e.from_id
WHERE e.to_id = 'cz_math_quad_vertex';
4.3 查询重型:Neo4j + Cypher(数据规模大、要复杂图算法时)
只有当你要“一键算出所有传递前置”“找最短补课路径”“算节点中心度做排序”这类多跳查询时,图数据库才值得上。它的查询语言 Cypher 表达链式关系非常自然:
// 建立关系
MATCH (a:Topic {id:'cz_math_completing_square'}),(b:Topic {id:'cz_math_quad_vertex'})
CREATE (a)-[:PREREQ {strength:'hard'}]->(b);
// 查"二次函数最值"的全部传递前置(不限层级)
MATCH (p:Topic)-[:PREREQ*1..]->(t:Topic {id:'cz_math_quad_vertex'})
RETURN p.name, p.grade ORDER BY p.grade;
// 查两个知识点之间最短的前置路径
MATCH p = shortestPath((a:Topic {name:'整式加减'})-[:PREREQ*..10]->(b:Topic {name:'二次函数的最值'}))
RETURN p;
选型建议:初中场景,99% 的情况 JSON 或 SQLite 就够。Neo4j 是 GPL-3.0 的 Java 服务(GitHub 约 1.72 万 Star),部署和维护成本对个人 / 学校来说偏高,收益有限。先跑通,别过度设计。
| 方案 | 适合 | 优点 | 代价 |
|---|---|---|---|
| JSON 文件 | 少于 2000 节点,只读 | 零依赖、可离线、单文件 | 查询要自己写代码 |
| SQLite | 要记录学生状态 | 免安装、单文件、SQL 查询 | 多跳查询写起来啰嗦 |
| Neo4j | 大规模 / 复杂图算法 | Cypher 表达多跳很自然 | 要跑服务、GPL-3.0、运维成本 |
| RDF/OWL + Jena | 要严格对齐课标语义 | 标准化、可推理 | 学习曲线陡,初中场景基本用不上 |
五、可视化:让老师点得动、学生看得懂
图能不能用,取决于交互。这一章先给库的选型,再给一份能直接跑的起步模板。
5.1 库选型对比(数据截至 2026-09-13,取自 GitHub 官方接口)
| 库 | 渲染 | Star / 许可 | 适合什么 |
|---|---|---|---|
| 3d-force-graph | 3D / WebGL(Three.js) | 6,392 · MIT | 想做 Marble 那种 3D 旋转效果,首选 |
| force-graph | 2D / Canvas | 2,118 · MIT | 2D 力导向,轻量、性能好 |
| sigma.js + graphology | 2D / WebGL | 12,164 · MIT / 1,744 · MIT | 节点成千上万时的 2D 方案 |
| Cytoscape.js | 2D / Canvas + SVG | 11,205 · MIT | 要图算法(最短路、连通性)时 |
| Apache ECharts | 2D / Canvas | 67,309 · Apache-2.0 | 顺带要仪表盘、统计图表时 |
| vis-network | 2D / Canvas | 3,626 · Apache-2.0 | 生态老牌,交互控件齐全 |
| D3.js | SVG / Canvas | 113,721 · ISC | 完全自定义布局,学习成本最高 |
| 原生 Canvas | 2D | — | 零依赖、绝对离线 |
一句话选型:想要效果最惊艳 → 3d-force-graph(Three.js 生态,MIT,可直接做单文件离线页)。想要零依赖、能塞进单文件 HTML、双击就能给学生看 → 原生 Canvas 手写(下面的起步模板就是)。两个都试试,看哪个更顺手。
5.2 三种呈现形态,对应三种用途
- 3D 螺旋 / 星球:像 Marble 和 cn-k12-math 那样,高度代表年级、颜色代表学科,可旋转。用途:总览 + 讲故事,一眼看出知识全貌,适合给家长 / 领导看。
- 2D 力导向:节点自动散开,点选高亮前置 / 后续。用途:诊断和路径规划,最实用,看局部关系最清楚。
- 层级树 / 列表:按教材章节展开成树,节点带“已掌握 / 待巩固 / 有风险”状态色。用途:进度追踪,接近老师熟悉的目录视图。
5.3 交互必备的四件事
- 点选高亮链:点一个节点,把它所有传递前置染黄、所有解锁染紫,其余变淡。这是知识图谱最核心的交互,没有它图就只是个漂亮摆件。
- 状态色:已掌握 / 待巩固 / 有风险 / 暂不建议,四色一眼区分。这是 Marble 的诊断思路——不在错的地方反复刷题。
- 标签避让:初中一张图几十个节点,标签必然重叠。做法是贪心避让——重叠的标签自动略过或错开(cn-k12-math 就实现了“垂直推移,最多 3 次,仍重叠则隐去”)。
- 详情面板:显示这个点的说明、掌握证据,以及一句“现在为什么该 / 不该学它”。
5.4 起步模板:一份可跑的单文件 HTML
下面这份代码零依赖、离线可用,复制保存为 .html,双击就能在浏览器打开。它做的事:加载数据 → 算力导向布局 → 画图 → 点选高亮前置 / 后续链。把 DATA 换成你自己的数据即可。
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<title>我的知识图谱</title><style>
body{margin:0;font-family:system-ui,'Microsoft YaHei',sans-serif;background:#f7f8fa}
.stage{display:grid;grid-template-columns:1fr 300px;height:100vh}
canvas{width:100%;height:100%;display:block;background:#fbfcff}
.info{padding:18px;overflow:auto;border-left:1px solid #e3e7ed;background:#fff}
</style></head><body>
<div class="stage"><canvas id="cv"></canvas><div class="info" id="info">点一个节点</div></div>
<script>
// ============ 1. 你的数据:改这里就行 ============
const DATA = {
topics: [
{ id:"t1", name:"整式加减", domain:"数与式" },
{ id:"t2", name:"整式乘法", domain:"数与式" },
{ id:"t3", name:"完全平方公式", domain:"数与式" },
{ id:"t4", name:"配方法", domain:"方程" },
{ id:"t5", name:"二次函数的最值", domain:"函数" }
],
edges: [
{ from:"t1", to:"t2", strength:"hard", reason:"整式加减是乘法的运算基础" },
{ from:"t2", to:"t3", strength:"hard", reason:"乘法公式由整式乘法推出" },
{ from:"t3", to:"t4", strength:"hard", reason:"配方依赖完全平方公式" },
{ from:"t4", to:"t5", strength:"hard", reason:"求最值的标准方法是配方" }
]
};
const COLOR = { "数与式":"#2563eb", "方程":"#059669", "函数":"#ea580c",
"图形与几何":"#7c3aed" }; // 按领域上色
// ============ 2. 力导向布局(Fruchterman-Reingold 简化版)============
const cv = document.getElementById('cv'), ctx = cv.getContext('2d');
let W=0, H=0, nodes=[], adj={}, pre={};
function resize(){
const r = cv.getBoundingClientRect(), dpr = devicePixelRatio||1;
W = r.width; H = r.height; cv.width = W*dpr; cv.height = H*dpr;
ctx.setTransform(dpr,0,0,dpr,0,0); draw();
}
function build(){
nodes = DATA.topics.map((t,i)=>({...t, x:0, y:0, vx:0, vy:0,
i, deg:0}));
const byId = new Map(nodes.map(n=>[n.id,n]));
DATA.edges.forEach(e=>{
const a=byId.get(e.from), b=byId.get(e.to); if(!a||!b) return;
a.deg++; b.deg++;
(adj[a.id] ||= []).push(b.id); // a 解锁 b
(pre[b.id] ||= []).push(a.id); // b 依赖 a
});
}
function layout(iters=420){
const cx=W/2, cy=H/2, k=Math.min(W,H)/Math.sqrt(nodes.length)*1.5;
nodes.forEach((n,i)=>{ const a=i/nodes.length*Math.PI*2;
n.x=cx+Math.cos(a)*k*2; n.y=cy+Math.sin(a)*k*2; });
const byId=new Map(nodes.map(n=>[n.id,n]));
for(let it=0; it<iters; it++){
const t = 1 - it/iters;
nodes.forEach(n=>{ n.dx=0; n.dy=0; });
// 斥力:对所有点
for(let i=0;i<nodes.length;i++) for(let j=i+1;j<nodes.length;j++){
const a=nodes[i], b=nodes[j];
let dx=a.x-b.x, dy=a.y-b.y, d=Math.hypot(dx,dy)||.01;
const f=k*k/d, ux=dx/d, uy=dy/d;
a.dx+=ux*f; a.dy+=uy*f; b.dx-=ux*f; b.dy-=uy*f;
}
// 引力:沿边
DATA.edges.forEach(e=>{
const a=byId.get(e.from), b=byId.get(e.to); if(!a||!b) return;
let dx=a.x-b.x, dy=a.y-b.y, d=Math.hypot(dx,dy)||.01;
const f=d*d/k, ux=dx/d, uy=dy/d;
a.dx-=ux*f; a.dy-=uy*f; b.dx+=ux*f; b.dy+=uy*f;
});
// 位移 + 向心收拢 + 降温
nodes.forEach(n=>{
n.dx += (cx-n.x)*0.045; n.dy += (cy-n.y)*0.045;
const d=Math.hypot(n.dx,n.dy)||.01, lim=Math.min(d,t*k*0.9);
n.x = Math.max(40, Math.min(W-40, n.x + n.dx/d*lim));
n.y = Math.max(34, Math.min(H-34, n.y + n.dy/d*lim));
});
}
}
// ============ 3. 绘制 + 高亮 ============
let sel=null, preSet=new Set(), nexSet=new Set(), hover=null;
function chain(start, map, acc=new Set()){
(map[start]||[]).forEach(id=>{ if(!acc.has(id)){ acc.add(id); chain(id,map,acc); } });
return acc;
}
function pick(mx,my){
let best=null, bd=17;
nodes.forEach(n=>{ const d=Math.hypot(n.x-mx,n.y-my); if(d<bd){bd=d;best=n;} });
return best;
}
function dim(id){
if(!sel) return 1;
if(id===sel.id) return 1;
if(preSet.has(id)||nexSet.has(id)) return 1;
return 0.16;
}
function draw(){
ctx.clearRect(0,0,W,H);
const byId=new Map(nodes.map(n=>[n.id,n]));
// 边
DATA.edges.forEach(e=>{
const a=byId.get(e.from), b=byId.get(e.to); if(!a||!b) return;
const hl = sel && (preSet.has(a.id)&&preSet.has(b.id) || nexSet.has(a.id)&&nexSet.has(b.id));
ctx.strokeStyle = hl ? (preSet.has(b.id)?'#f0b429':'#a78bfa') : '#dfe5ee';
ctx.lineWidth = hl?2.2:1.1;
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
});
// 标签(贪心避让)
const placed=[];
const fits=(x,y,w,h)=>!placed.some(p=> Math.abs(p.x-x)<(p.w+w)/2+4 && Math.abs(p.y-y)<(p.h+h)/2+2);
// 节点
nodes.forEach(n=>{
const o=dim(n.id), r = sel&&n.id===sel.id ? 12 : (9 + Math.min(4, n.deg*0.7));
ctx.globalAlpha=o;
ctx.beginPath(); ctx.arc(n.x,n.y,r,0,Math.PI*2);
ctx.fillStyle = preSet.has(n.id)?'#f0b429' : nexSet.has(n.id)?'#a78bfa'
: (COLOR[n.domain]||'#64748b');
ctx.fill();
ctx.lineWidth=1.6; ctx.strokeStyle='#fff'; ctx.stroke();
});
// 标签层
nodes.forEach(n=>{
const o=dim(n.id); if(o<0.4) return;
ctx.globalAlpha=o; ctx.font='600 12.5px system-ui,sans-serif';
ctx.textAlign='center'; ctx.textBaseline='bottom';
const w=ctx.measureText(n.name).width, x=n.x, y=n.y-14;
if(fits(x,y,w,14)){ placed.push({x,y,w,h:14});
ctx.fillStyle='#1b1f24'; ctx.fillText(n.name,x,y); }
});
ctx.globalAlpha=1;
}
// ============ 4. 交互 ============
cv.addEventListener('click', ev=>{
const r=cv.getBoundingClientRect(), n=pick(ev.clientX-r.left, ev.clientY-r.top);
if(!n){ sel=null; preSet=new Set(); nexSet=new Set();
document.getElementById('info').innerHTML='点一个节点'; draw(); return; }
sel=n; preSet=chain(n.id,pre); nexSet=chain(n.id,adj);
const byId=new Map(nodes.map(x=>[x.id,x]));
const nm=id=>(byId.get(id)||{}).name||id;
document.getElementById('info').innerHTML =
'<h3>'+n.name+'</h3>'
+ '<p style="color:#7b8794;font-size:12.5px">'+n.domain+'</p>'
+ '<p style="font-size:12px;font-weight:700;color:#a35a05;margin:12px 0 4px">必须先会('
+preSet.size+')</p>'+[...preSet].map(i=>'<span style="font-size:12px">'+nm(i)+'</span>').join(' · ')
+ '<p style="font-size:12px;font-weight:700;color:#5b2fd6;margin:12px 0 4px">学会后解锁('
+nexSet.size+')</p>'+[...nexSet].map(i=>'<span style="font-size:12px">'+nm(i)+'</span>').join(' · ');
draw();
});
cv.addEventListener('mousemove', ev=>{
const r=cv.getBoundingClientRect(), n=pick(ev.clientX-r.left, ev.clientY-r.top);
cv.style.cursor = n?'pointer':'default';
});
addEventListener('resize', resize);
// 启动
build(); resize(); layout(); draw();
</script></body></html>
六、教学用法:三个能立刻用上的场景
图搭出来只是开始。下面三个场景,是这张图真正开始“干活”的地方。
场景一 · 诊断:把“这道题不会”翻译成“这一环断了”
学生做错一道“二次函数求最值”的题。传统做法是再出 10 道同类题,结果他继续错、越来越挫败。图谱的做法是回到上游找根因:
- 定位节点:二次函数的最值 → 标记为“有风险”
- 回溯前置链:配方法 → 完全平方公式 → 整式乘法 → 整式加减
- 逐个点选、逐个问一句
assessmentPrompt,找到第一个熄掉的环——比如发现他“完全平方公式”中间项的系数老是忘 - 不补最值,先补那一环;补完再回到最值,题自然就会了
这正是 Marble 那句话的意思:“找到孩子真正卡住的地方,而不是在错误的地方反复刷题。” 对初中生同样成立,甚至更关键——初中知识链更长,一个断点能拖垮一整章。
场景二 · 路径:新课前给学生一张“通关路线”
讲新章节前,用图生成一条最小前置路径:只列出这节课真正依赖的那几个节点。学生看到的是“只要先搞定这 3 个点,就能跟得上”,而不是一张望而生畏的全图。配套设计:
- 节点上标四色状态:已掌握 / 待巩固 / 有风险 / 暂不建议学
- 点击任一节点,显示“现在为什么该学它 / 为什么暂不建议学它”
- 老师端用同一张图做备课进度:这章的前置点班里整体掌握率是多少
场景三 · 复习:一张“知识体检单”
期末前,把整册的节点铺开,让学生自评或做小测,把每个点染成四色,于是得到一张薄弱点热力图。然后按图规划复习顺序——先补图中的上游,再补下游,而不是按章节顺序从头刷。
给老师的一个提醒:如果要按班统计,注意别把图变成“排名工具”。图谱的价值在于指向下一步该做什么,不是给学生贴标签。状态色建议只用“已掌握 / 待巩固 / 有风险 / 暂不建议”,不要用分数。
七、版权与合规:别在最后一步踩坑
| 对象 | 许可 | 能不能商用 | 要求 |
|---|---|---|---|
| Marble 数据库(结构、ID、关系) | ODbL 1.0 | 可以 | 署名;若你修改 / 扩展了这套 taxonomy 本身,衍生数据库要同样开源 |
| Marble 文字内容(描述、证据、理由) | CC BY-SA 4.0 | 可以 | 署名 + 相同方式共享 |
| Marble 的课标文件 | 各上游自己的许可 | 看上游 | Marble 无权转授,需逐一看来源 |
| cn-k12-math 数据 | CC BY-NC-SA 4.0 | 不可以(NC) | 署名 + 非商业 + 相同方式共享 |
| cn-k12-math 代码 | Apache 2.0 | 可以 | 保留版权声明 |
Marble 的 ODbL 有个关键区分,很多人会看错:
- 衍生数据库(你直接修改 / 扩展这套 taxonomy)→ 必须保持 ODbL 开源。
- 产出作品(把数据用在你的 App、模型、产品里)→ 仍是你自己的,可以闭源。
换句话说:你可以用它在自己做的教学工具里,产品不必开源;但如果你改进了“这张图本身”,改进部分要开源共享。前提都是——署名。
署名格式(Marble 官方要求):
Marble Skill Taxonomy (v1) · © Generative Spark, Inc. (Marble) · https://withmarble.com
licensed under ODbL 1.0 (database) and CC BY-SA 4.0 (content).
教材内容本身:知识点名称、章节标题这类信息通常不受著作权保护,你可以整理成图谱,这也是 cn-k12-math 敢做 1–9 年级数学的底气。但不要整段抄教材正文、例题和插图。文字描述、掌握证据这些,自己写或让 AI 生成后人工改写。
八、30 天落地路线图
别一上来就想把三年六册全做完。按下面这个节奏,四周能出一个可用的版本。
| 阶段 | 目标 | 产出 | 关键提醒 |
|---|---|---|---|
| 第 1 周 选题定骨架 |
选定一册教材(如九上),定义字段,拆出微主题 | topics.json,约 60–100 个点 |
粒度宁细勿粗;每点先填 name / grade / domain / type 四项 |
| 第 2 周 连边审核 |
抽前置关系,逐条人工复核,跑 DAG 校验 | edges.json,每边带 reason |
宁缺毋滥;不确定的标 pending,不要硬连 |
| 第 3 周 跑通可视化 |
用第 5 章模板渲染,实现点选高亮 + 详情面板 | 一个单文件 HTML,双击可用 | 先 2D 力导向,够用;想炫再加 3D |
| 第 4 周 试用迭代 |
给 1–2 位老师试用,收集“哪个连线不对 / 不好用” | 修订后的第二版 | 老师挑出的错边,比你自己检查有用十倍 |
判断“做成了”的标准:不是图画得多漂亮,而是——当学生说“我不会”时,你和学生能一起顺着图,在 2 分钟内定位到一个具体的可补的节点。做到这一点,这个图就成功了。
附录:提示词、校验脚本、参考资源
A. AI 抽取前置关系的提示词模板
你是一位初中数学教研员。下面是一份初中数学微主题清单(JSON 数组,每项含 id/name/grade)。
任务:判断这些主题之间的【前置依赖】关系。
规则:
1. 只输出 JSON 数组,不要任何解释文字。
2. 每项格式:{"from":"前置id","to":"后续id","strength":"hard|soft","reason":"不超过30字的原因"}
3. strength 规则:不会 A 就无法学习 B → hard;学会 A 有助于 B 但不必须 → soft。
4. 宁缺毋滥。不确定的关系不要输出。
5. 每条 reason 必须能独立说清"为什么必须先学它"。
6. 禁止产生环(A→B→A)。若发现循环,只保留更基础的那个方向。
主题清单:
{{把 topics.json 的 topics 数组贴在这里}}
参考:下面是一段已确认的边作为风格示例
{"from":"整式加减","to":"整式乘法","strength":"hard","reason":"整式加减是展开乘法运算的基础"}
B. 校验脚本(Node.js,检查 ID 重复、悬空引用、环)
// check.mjs —— 用法:node check.mjs data.json
import fs from 'node:fs';
const data = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
const ids = new Set(); let err = 0;
// 1. ID 重复
for (const t of data.topics) {
if (ids.has(t.id)) { console.error('✗ 重复 ID:', t.id); err++; }
ids.add(t.id);
}
// 2. 悬空引用
for (const e of data.edges) {
if (!ids.has(e.from)) { console.error('✗ 悬空 from:', e.from); err++; }
if (!ids.has(e.to)) { console.error('✗ 悬空 to :', e.to); err++; }
}
// 3. 查环(DFS 三色标记)
const adj = {};
for (const e of data.edges) (adj[e.from] ||= []).push(e.to);
const color = {}; // 0=未访问 1=在栈中 2=已完成
let hasCycle = false;
function dfs(u, path) {
color[u] = 1;
for (const v of (adj[u] || [])) {
if (color[v] === 1) { console.error('✗ 发现环:', [...path, u, v].join(' → ')); hasCycle = true; }
else if (color[v] === undefined) dfs(v, [...path, u]);
}
color[u] = 2;
}
for (const id of ids) if (color[id] === undefined) dfs(id, []);
// 4. 统计
console.log('节点:', ids.size, '| 边:', data.edges.length);
console.log(err ? '✗ 有 ' + err + ' 个致命错误' : '✓ 引用完整');
console.log(hasCycle ? '✗ 图中有环,必须修复' : '✓ 是合法 DAG');
C. 参考资源
| 资源 | 地址 | 用途 |
|---|---|---|
| Marble Skill Taxonomy | github.com/withmarbleapp/os-taxonomy | 字段契约范本(ODbL 1.0) |
| Marble 官网 3D 图 | withmarble.com/curriculum | 看 3D 交互效果 |
| 中国 K12 数学图谱 | github.com/haojing8312/cn-k12-math-knowledge-graph | 初中数学底稿(非商业) |
| 3d-force-graph | github.com/vasturiano/3d-force-graph | 3D 可视化(MIT) |
| Apache ECharts | github.com/apache/echarts | 2D 图 + 统计图表(Apache-2.0) |
| K12-KGraph 数据集 | huggingface.co/datasets/lhpku20010120/K12-KGraph | cn-k12-math 的上游数据源 |
参考来源
- Marble Skill Taxonomy 官方 README 与 GitHub 接口(项目规模、字段示例、许可与署名要求),核实日期 2026-09-13。
- cn-k12-math-knowledge-graph 官方 README(四级结构、数据规模、边来源、许可),核实日期 2026-09-13。
- 各可视化库的 GitHub 官方接口(Star 数、许可),核实日期 2026-09-13。
说明:本文为教学用途整理。文中引用的第三方数据与文字,使用与再分发时请遵守其各自许可(Marble 与 cn-k12-math 的具体要求见第七章)。

