TikZ 实时图形
实时图形是一种读者可以改变的 TikZ 绘图。其目的与 tikz-dl-visualizer 中的静态图形相同(一个想法,一目了然),但读者现在做某些操作并观察想法如何响应:拖拽起点,看到动量抵消折线;降低温度,看到 softmax 坍缩为 argmax。
每个图形由两半部分组成:
| 文件 | 包含 | 编译方式 |
|---|---|---|
figure.tex | 所有从不改变的内容:坐标轴、等高线、网格、固定值的矩阵、标签、方程,以及实时文本的插槽 | pdflatex,一次 |
figure.js | 所有改变的内容:参数、手柄、热区、纯 compute(state) 函数以及 draw(d, state, c) | 浏览器,每次更改时 |
scripts/build.py 将 .tex 编译两次(一次正常编译,一次使用 ID 颜色的实时文本以便裁剪那些字形),导出 TikZ 坐标框架和命名节点框,在 node 下运行规范,并写入:
figure.html:一个自包含页面(无需网络)。打开它、iframe 嵌入它或发布它。figure.png加上figure.<snapshot>.png:默认状态和每个快照,无需浏览器即可渲染。这些是你查看自己作品的方式。figure.svg:默认状态的静态 SVG,用于论文和后备方案。
包含:
assets/dlviz.sty:调色板和 TikZ 样式(与 tikz-dl-visualizer 相同的文件)。assets/dllive.sty:TeX 端,包含export和live=键以及\dlnum。assets/dllive.js:运行时:坐标映射、绘图 API、滑块、手柄、热区、播放。assets/examples/momentum/:参数、可拖拽手柄、播放按钮、实时数字、移动标签。assets/examples/attention/:导出的单元格、悬停热区、热力图、29 个实时数字、曲线。references/api.md:完整 API(TeX 键、规范字段、绘图调用)。在会话中编写第一个规范之前阅读它。references/gotchas.md:我们遇到的问题及其修复。当某些东西看起来不对劲时阅读它。
对于视觉语言本身(颜色角色、符号、布局、标注),请遵循 tikz-dl-visualizer:其 references/visual-language.md 和 references/archetypes.md 原样适用。如果本次会话尚未阅读,请阅读它们。
工作流程
1. 写出交互描述句
在任何代码之前,写一句形式为 “读者做 X 并看到 Y,这展示了 Z” 的句子:
- “拖拽起点并改变 β:穿越山谷的折线被抵消,而沿山谷的运动累积;β 过大导致过冲。”
- “悬停一个 token:其 A 行是权重集合,其输出行是 V 行的加权平均。降低 τ,权重坍缩到一行。”
如果 Y 在静态图中同样清晰,则制作静态图(tikz-dl-visualizer)。使用一到三个控件。每个控件必须改变眼睛能跟随的内容;一个效果无法看到的滑块只是噪音。
2. 拆分静态与实时
在纸上画出变化的内容。其他所有内容都放入 .tex 文件中。典型的拆分:
- 静态:坐标轴、等高线、网格、单元格轮廓、固定矩阵、token 标签、方程、读出标签如
\(\beta = {}\)、图例线条。 - 实时:路径、点、高亮、热力图填充、箭头、变暗以及数字(作为
\dlnum插槽,以便保持真实的 TeX 字形)。 - 必须移动的标签(例如附着在手柄上的标签)也是
live=节点,通过d.label(id, {from, to})移动。
3. 编写 figure.tex
从最接近的示例开始。必需的:
\documentclass[tikz,border=8pt]{standalone}
\usepackage[dark]{dlviz} % [light] 用于白底页面
\usepackage{dllive}
\begin{document}
\begin{tikzpicture}[x=1.15cm, y=1.15cm]
\useasboundingbox (xmin,ymin) rectangle (xmax,ymax); % 始终:实时绘图必须在此范围内
...静态绘图...
\node[cell=coloraqua, export] (A-1-1) at (..) {}; % export:JS 可以访问此框
\node[fit=(Q-1-1)(Q-1-3), inner sep=0, export] (Qrow-1) {}; % 用于热区的不可见框
\node[anchor=base west, inner sep=0, text=coloraqua] (b) at (..) {\(\beta = {}\)};
\node[live=beta, anchor=base west, inner sep=0, text=coloraqua] at (b.base east) {\(\dlnum\)};
\end{tikzpicture}
\end{document}
- 使用
x=/y=(而不是scale=)设置缩放,使得 JS 中的 1 单位等于 TikZ 中的 1 单位。 - 实时数字节点只包含
\(\dlnum\)。使用text=设置其颜色,使用font=设置其大小;节点内部的颜色命令会破坏它。 - 在表格单元格中放置数字时,使用
anchor=mid west位于单元格中心,然后使用d.number(id, v, {at: "cell"})使其居中。在\matrix或任何布局重要的地方,使用\dlnumin{2em}使插槽只保留该宽度。 - 如果图形仅在选择某些内容时才有意义,则以此方式打开它:
initial: {hover: "..."}加上stickyHover: true。 - 命名实体以便 JS 可以循环遍历它们(
A-\i-\j,Qrow-\i)。 - 在顶部放置
% Caption:注释,如同 tikz-dl-visualizer 中一样。
4. 编写 figure.js
DLLive.figure({
title: "...", caption: "...交互方式,一两句话...",
params: { beta: { label: "β", min: 0, max: 0.99, step: 0.01, value: 0.6 } },
handles: { w0: { value: [-4, 1.5], min: [-5.2, -1.85], max: [5.2, 1.85] } },
hotspots: /^Qrow-\d$/, // 设置 state.hover 的导出节点
play: { param: "t", ms: 110 }, // 可选:动画化一个(隐藏的)参数
compute: (s) => ({ path: run(s.beta, s.w0) }), // 纯数学,已记忆化
draw(d, s, c) { // TikZ 单位,dlviz 颜色
d.path(c.path, { color: "aqua", width: "thick", dots: true });
d.number("beta", s.beta, { digits: 2 });
},
snapshots: [{ name: "overshoot", beta: 0.95 }], // 额外的 PNG 用于检查
tests: [{ name: "matches numpy", state: { beta: 0.4 }, check: (c) => Math.abs(c.path[30][0] - 1.036e-5) < 1e-8 || `got ${c.path[30]}` }],
});
- 数学逻辑存在于
compute中,且必须正确。 先用 Python(numpy)写一个参考实现,然后添加一个tests条目,将 JS 与你从 Python 计算出的数字进行比较。永远不要相信仅凭头脑写出的数学逻辑。 draw仅绘图:无状态更改,无随机性(如果需要噪声,在compute中使用种子 RNG),且每帧都运行。- 使用 dlviz 颜色角色(
aqua、red、orange!55!bg...)和 TikZ 宽度(thin、thick、very thick),使叠加层看起来如同支架。 - 为每个重要的状态添加快照:每个滑块的极端值、悬停状态、动画中间帧。重叠问题由此发现。
5. 构建
uv run --with pymupdf python <skill-dir>/scripts/build.py path/to/figure.tex
它会打印导出的节点名称和实时插槽、热区匹配的节点数量、每个测试的 PASS/FAIL,以及针对页面外部绘图或 TeX 文本被其边缘裁剪的 WARN。错误会指明有问题的插槽、节点或参数。仅更改 .js 后,添加 --no-tex(大约 1 秒)。
6. 查看、批评、迭代
打开它写的每个 PNG(使用 Read 工具)并检查,如同对待静态图形:
- 实时部分是否与支架对齐(路径起点在正确点,数字位于其单元格内,高亮紧贴其行)?
- 在每个快照中,是否有任何重叠:穿过读出的路径、跨越数字的曲线、超出单元格的文本、被边缘裁剪的内容?
- 控件的极端值是否仍然可读(β = 0.99、τ = 0.2、角落中的手柄)?
- 在默认状态下,眼睛是否能在三秒内找到想法?默认状态是缩略图和快速浏览的读者所看到的。
- 颜色在支架和叠加层中是否保持其角色。
修复并重新构建。通常需要两到三轮。
然后,如果你有浏览器(内置浏览器窗格),尝试实际页面:它通常拒绝 file://,因此需要提供文件夹服务,例如使用 .claude/launch.json 条目 {"name": "fig", "runtimeExecutable": "python3", "runtimeArgs": ["-m", "http.server", "8800", "--directory", "<folder>"], "port": 8800} 和 preview_start。拖拽、滑动、悬停和播放;在控制台中,DLLive.state() 显示当前状态。快照不显示虚线或悬停光标。
7. 交付
向用户提供 PNG(默认状态)、figure.html 的路径以及简短的阅读指南:首先尝试什么以及观察什么(“将 w₀ 拖入陡峭方向,然后提高 β...”)。如果他们想要一个可共享的链接,将 HTML 作为 Artifact 发布;它是自包含的,因此不需要额外的文件。用一句话提到下一个自然的控件或图形。
嵌入
- 一个页面上的多个图形,或具有自身亮/暗主题的页面(tikz-explorer-book 通过其
tools/fig.sh实现):使用--themes dark,light --embed构建。这会以两种主题编译支架,并写入figure.live.html,一个片段(<div class="dl-live">加上一个<script>DLLive.load("name", ...)</script>)。宿主页面在片段之前包含一次assets/dllive.js,并包含assets/template.html中标记为“live figure”的 CSS 规则。它使用--show-d/--show-l显示当前主题的支架。运行时遵循页面主题(<html>上的data-theme="dark|light",或系统设置)并重新着色叠加层。也会写入-light.png。 - 独立或作为 Artifact:按原样使用
figure.html。使用--themes dark,light时,它遵循查看器的主题;否则保持.tex的主题。 - 幻灯片:使用
figure.png/figure.svg作为静态幻灯片,并链接 HTML。 - 论文:使用
figure.svg或.dllive/中的支架 PDF;静态默认状态本身就是一个完整图形。对于仅打印的图形,tikz-dl-visualizer 是更好的工具。