🧬 二代基因组测序算法可视化
技术说明文档🧬 Next-Generation Sequencing Algorithm Visualization
Technical Documentation
本文档详尽说明项目的整体架构、每个源文件的职责、以及所有核心算法和渲染逻辑的实现原理。
This document provides a comprehensive overview of the project architecture, file responsibilities, and all core algorithm and rendering logic implementations.
项目概述Project Overview
本项目是一个纯前端的交互式教学应用,将 Illumina 二代基因组测序(Next-Generation Sequencing, NGS)的完整生信分析流程拆解为 6个可视化步骤,每个步骤配有动画演示和原理讲解,支持手动逐帧浏览和自动播放。
This project is a pure front-end interactive educational application that breaks down the complete Illumina Next-Generation Sequencing (NGS) bioinformatics pipeline into 6 visualized steps, each with animated demonstrations and explanations, supporting both manual frame-by-frame browsing and auto-play.
设计目标Design Goals
- 教育性:以简化的 mock 数据集展示算法的核心逻辑,而非实际生产代码
- Educational: Uses simplified mock datasets to demonstrate core algorithm logic, rather than production code
- 交互性:每个步骤可独立播放、暂停、回退、重放,降低学习门槛
- Interactive: Each step can be independently played, paused, rewound, and replayed, lowering the learning barrier
- 零依赖:不依赖任何第三方库(无 React/Vue/D3.js),便于理解和二次修改
- Zero Dependencies: No third-party libraries (no React/Vue/D3.js), easy to understand and modify
- 可扩展:步骤以数组对象定义,新增步骤只需追加一个配置对象
- Extensible: Steps are defined as array objects; adding a new step only requires appending a configuration object
覆盖的NGS流程NGS Pipeline Coverage
技术选型Technology Stack
| 技术Technology | 版本/规范Version/Spec | 用途Purpose |
|---|---|---|
| HTML5 | Living Standard | 页面结构、SVG 容器Page structure, SVG containers |
| SVG 1.1 | W3C Rec. | 全部可视化图形渲染(内联生成)All visualization rendering (inline generation) |
| CSS3 | — | 布局(Flexbox)、主题变量、过渡动画Layout (Flexbox), theme variables, transitions |
| Vanilla JS | ES2020 | 渲染逻辑、动画控制、DOM 交互Rendering logic, animation control, DOM interaction |
技术架构Architecture
目录结构Directory Structure
NGS可视化/
├── index.html # 应用主入口,HTML 骨架
├── docs.html # 本技术说明文档(独立页面)
├── css/
│ └── style.css # 全局样式 + CSS 变量主题系统
└── js/
├── data.js # Mock 基因组数据 + 数据生成函数
└── app.js # 主程序:SVG引擎 + 步骤渲染 + 动画控制
模块依赖关系Module Dependencies
/* 加载顺序(index.html script标签顺序) */
data.js // ① 先加载:定义全局常量 BASE_COLOR, REF, READS 等
↓
app.js // ② 后加载:依赖 data.js 的所有全局变量
BASE_COLOR、REF、READS 等),因此 data.js 必须在 app.js 之前加载。这是 "无构建工具" 架构的典型约束。
⚠️ The two JS files share data through global variables (BASE_COLOR, REF, READS, etc.), so data.js must be loaded before app.js. This is a typical constraint of "no build tool" architecture.
渲染架构(快照模型)Rendering Architecture (Snapshot Model)
本项目采用 "快照帧" 渲染模式,而非连续动画帧(requestAnimationFrame):
This project uses a "snapshot frame" rendering model, rather than continuous animation frames (requestAnimationFrame):
- 每个步骤定义若干离散的 substep(子步骤帧)
- Each step defines several discrete substeps (sub-step frames)
- 每次状态变化时,重新生成整个 SVG 内容字符串并写入
innerHTML - On each state change, the entire SVG content string is regenerated and written to
innerHTML - 帧间切换由
setTimeout驱动,间隔 2200ms(可调) - Frame transitions are driven by
setTimeout, with a 2200ms interval (configurable) - 好处:逻辑简单,每帧状态完全独立,易于调试;代价是不支持连续过渡动画
- Advantage: simple logic, fully independent frame states, easy to debug; trade-off is no continuous transition animations
// 核心渲染循环(简化)
function render() {
const step = STEPS[curStep];
document.getElementById('main-svg').innerHTML
= step.render(curSub); // ← 调用对应步骤的渲染函数
updateStepNav();
updateSubstepDots();
updateButtons();
}
状态模型State Model
应用仅维护4个顶层状态变量,所有 UI 都是这4个变量的纯函数:
The application maintains only 4 top-level state variables; all UI is a pure function of these 4 variables:
let curSub = 0; // 当前子帧索引 [0, substepLabels.length-1]Current sub-frame index [0, substepLabels.length-1]
let isPlaying = false; // 是否处于自动播放状态Whether auto-play is active
let playTimer = null; // setTimeout 句柄(用于取消)setTimeout handle (for cancellation)
数据流转Data Flow
从原始 mock 数据到屏幕像素,数据经过以下变换链:
From raw mock data to screen pixels, data goes through the following transformation chain:
data.js 定义
REF (string) // "ATCGAACGTTACGGATCGATCGATGAATCGATCGATCGA"
READS (Array<ReadObj>) // 9条Read对象
SIGNAL_DATA (Array) // 8个位置的荧光强度
↓
app.js 渲染函数 renderStepN(substep)
→ 布局计算(坐标、宽高)
→ 调用 SVG 工具函数 R/T/L/C/P
→ 拼接 SVG 字符串
↓
render() 函数
→ svg.innerHTML = svgString // 写入 DOM
↓
浏览器解析 SVG → 光栅化 → 显示
0 0 800 380 为坐标系。浏览器通过 preserveAspectRatio="xMidYMid meet" 自动缩放以适应容器,保持宽高比不变。
All coordinates use the SVG viewBox 0 0 800 380 coordinate system. The browser auto-scales via preserveAspectRatio="xMidYMid meet" to fit the container while maintaining the aspect ratio.
文件:index.htmlFile: index.html
应用的唯一 HTML 骨架文件,仅负责结构,不含任何业务逻辑。所有动态内容由 JS 在 DOMContentLoaded 事件后注入。
The sole HTML skeleton file for the application, responsible only for structure with no business logic. All dynamic content is injected by JS after the DOMContentLoaded event.
关键 DOM 节点及职责Key DOM Nodes and Responsibilities
| ID / 选择器ID / Selector | 职责Responsibility | 内容来源Content Source |
|---|---|---|
#step-bar-inner | 步骤导航标签容器Step navigation tab container | JS updateStepNav() 生成Generated by JS updateStepNav() |
#main-svg | 主可视化画布(SVG元素,viewBox 800×380)Main visualization canvas (SVG element, viewBox 800×380) | JS render() 写入 innerHTMLJS render() writes innerHTML |
#step-title | 当前步骤标题Current step title | JS render() 写入Written by JS render() |
#step-desc | 步骤简介文字Step description text | JS render() 写入Written by JS render() |
#substep-dots | 子帧进度点容器Sub-frame progress dot container | JS updateSubstepDots() 生成Generated by JS updateSubstepDots() |
#substep-label | 当前子帧文字说明Current sub-frame text description | JS updateSubstepDots() 写入Written by JS updateSubstepDots() |
#theory-content | 右侧原理讲解面板Right-side theory explanation panel | JS render() 写入 innerHTMLJS render() writes innerHTML |
#btn-play/prev/next/... | 控制按钮(5个)Control buttons (5) | 静态 HTML,JS 绑定事件Static HTML, JS binds events |
脚本加载顺序Script Loading Order
<!-- 放在 </body> 前,确保DOM已就绪 -->
<script src="js/data.js"></script> <!-- 必须第一 -->
<script src="js/app.js"></script> <!-- 必须第二 -->
采用同步加载(非 defer/async),顺序保证 data.js 的全局变量在 app.js 执行前就绪。实际触发渲染在 DOMContentLoaded 回调中。
Synchronous loading (no defer/async) is used to guarantee that data.js global variables are ready before app.js executes. Actual rendering is triggered in the DOMContentLoaded callback.
文件:css/style.cssFile: css/style.css
全局样式表,定义了整个应用的视觉主题、布局结构和组件样式。
Global stylesheet defining the visual theme, layout structure, and component styles for the entire application.
CSS 变量系统(主题)CSS Variable System (Theming)
所有颜色通过 :root 中的 CSS 自定义属性(CSS Variables)统一管理,修改单一变量即可全局生效:
All colors are managed through CSS custom properties (CSS Variables) in :root; changing a single variable takes effect globally:
:root {
/* 背景层次(3层深度)*/
--bg: #0d1117; /* 最深层:页面背景 */
--bg2: #161b22; /* 中层:面板、导航栏 */
--bg3: #1c2333; /* 浅层:卡片、代码块 */
/* 功能色 */
--accent: #388bfd; /* 主强调色(蓝)*/
--green: #3fb950; /* 成功/完成 */
--red: #f85149; /* 错误/错配 */
--amber: #d29922; /* 警告/注意 */
/* DNA碱基颜色(与 data.js BASE_COLOR 对应) */
--A: #3fb950; /* 腺嘌呤 Adenine → 绿 */
--T: #f85149; /* 胸腺嘧啶 Thymine → 红 */
--C: #388bfd; /* 胞嘧啶 Cytosine → 蓝 */
--G: #d29922; /* 鸟嘌呤 Guanine → 黄/琥珀 */
}
--A/T/C/G,用于 HTML 面板中的 .bA/.bT/.bC/.bG 标签)和 JS(BASE_COLOR 对象,用于 SVG 渲染)中定义,两者保持一致,修改时需同步更新。
Base colors are defined in both CSS (--A/T/C/G, for .bA/.bT/.bC/.bG tags in HTML panels) and JS (BASE_COLOR object, for SVG rendering); they must be kept in sync when modified.
布局系统Layout System
整体采用三层 Flexbox 布局:
The overall layout uses a three-level Flexbox structure:
#app /* flex-direction: column(垂直主轴)*/
├── header /* flex-shrink: 0,固定高度 */
├── #step-bar /* flex-shrink: 0,步骤导航 */
├── #content /* flex: 1(水平Flex,占满剩余高度)*/
│ ├── #vis-panel /* flex: 1,自动填充 */
│ └── #theory-panel /* width: 300px,固定宽度 */
└── #controls-bar /* flex-shrink: 0,固定高度 */
关键属性:body { height: 100%; overflow: hidden; } 配合 #app { height: 100vh; },使整个应用在视口内不滚动,内容区可独立滚动(#theory-panel 有 overflow-y: auto)。
Key properties: body { height: 100%; overflow: hidden; } combined with #app { height: 100vh; } keeps the entire app within the viewport without scrolling; the content area scrolls independently (#theory-panel has overflow-y: auto).
主要组件类Main Component Classes
| 类名Class Name | 用途Purpose |
|---|---|
.step-item | 步骤导航标签,通过 .active / .done 切换状态样式Step navigation tab, switches state styles via .active / .done |
.sdot | 子帧进度圆点,点击可跳帧Sub-frame progress dot, clickable to jump to frame |
.ctrl-btn | 控制按钮基础样式;.primary 变体为主操作按钮;.playing 变体显示紫色暂停状态Control button base style; .primary variant for main action; .playing variant shows purple pause state |
.tsec / .hbox / .hbox-g | 原理面板内的文本段落和信息框组件Text paragraphs and info box components in the theory panel |
.btag .bA/T/C/G | 碱基内联标签(用于 HTML 文本中的彩色碱基徽标)Base inline tags (colored base badges in HTML text) |
文件:js/data.jsFile: js/data.js
全局 Mock 数据层。定义参考序列、测序读段及衍生数据,完全无副作用(仅声明常量和工具函数),无 DOM 操作。
Global mock data layer. Defines reference sequences, sequencing reads, and derived data, completely side-effect free (only declares constants and utility functions), no DOM operations.
全局常量一览Global Constants Overview
| 常量名Constant | 类型Type | 说明Description |
|---|---|---|
BASE_COLOR | Object<string,string> | 碱基→十六进制颜色映射,被所有渲染函数调用Base → hex color mapping, used by all rendering functions |
COMPLEMENT | Object<string,string> | Watson-Crick 互补碱基映射(A↔T, C↔G)Watson-Crick complementary base mapping (A↔T, C↔G) |
REF | string | 39bp 模拟参考基因组序列39bp simulated reference genome sequence |
SNP_POS | number | SNP 位点(0-based 索引 = 24)SNP position (0-based index = 24) |
SNP_REF | string | 参考等位基因 'G'Reference allele 'G' |
SNP_ALT | string | 变异等位基因 'A'Alternate allele 'A' |
READS | Array<ReadObj> | 9条测序读段对象数组Array of 9 sequencing read objects |
SBS_TEMPLATE | string | 步骤3 SBS 演示用模板(REF前12bp)Step 3 SBS demo template (first 12bp of REF) |
SBS_GROWING | string[] | SBS 合成链碱基序列(互补链)SBS growing chain base sequence (complementary strand) |
SIGNAL_DATA | Array<{base,signals}> | 步骤4 模拟荧光强度数据(8个循环)Step 4 simulated fluorescence intensity data (8 cycles) |
算法:genQual — 伪随机质量分生成Algorithm: genQual — Pseudo-random Quality Score Generation
function genQual(readId, pos) {
const v = (readId * 17 + pos * 31 + 7) % 23;
return 18 + v; // 返回 Q18 ~ Q40
}
目的:生成视觉上有变化但可复现的质量分数,避免每次刷新随机值导致画面抖动。
Purpose: Generate visually varied but reproducible quality scores, avoiding frame jitter from random values on each refresh.
原理:线性同余公式 (readId×17 + pos×31 + 7) % 23 以 readId 和碱基位置 pos 为种子,产生 0~22 的确定性伪随机数,加上基础值 18 得到 Q18~Q40 的 Phred 分数范围。选用质数 17、31 是为了减少同余值的规律性重复。
Principle: The linear congruential formula (readId×17 + pos×31 + 7) % 23 uses readId and base position pos as seeds to produce deterministic pseudo-random numbers from 0 to 22, plus a base value of 18 to yield Phred scores in the Q18–Q40 range. Primes 17 and 31 are chosen to reduce periodic repetition in congruential values.
算法:makeRead — 读段对象构造Algorithm: makeRead — Read Object Construction
function makeRead(id, start, len, muts = []) {
// 1. 从参考序列切片
const bases = REF.slice(start, start + len).split('');
// 2. 注入指定的突变/错误(相对位置)
muts.forEach(([relPos, alt]) => { bases[relPos] = alt; });
// 3. 生成每个位置的质量分数
const seq = bases.join('');
const qual = bases.map((_, i) => genQual(id, i));
return { id, start, seq, qual, len: seq.length, muts };
}
muts 参数格式:[ [relPos, altBase], ... ],relPos 是相对于 read 起始位置的偏移量。
muts parameter format: [ [relPos, altBase], ... ], where relPos is the offset relative to the read start position.
SNP 注入逻辑:对于覆盖位置 24(SNP_POS)的 reads,将该位置的绝对坐标减去 start 得到相对位置,再将对应碱基改为 SNP_ALT ('A'):
SNP injection logic: For reads covering position 24 (SNP_POS), subtract start from the absolute coordinate to get the relative position, then change the corresponding base to SNP_ALT ('A'):
// 例:Read 6,start=19,SNP全局位置24,相对位置 = 24-19 = 5
makeRead(6, 19, 15, [[SNP_POS-19, SNP_ALT]])
// ↑ = [5, 'A'] → 第6个碱基 G→A
SIGNAL_DATA 生成逻辑SIGNAL_DATA Generation Logic
const SIGNAL_DATA = SBS_GROWING.slice(0, 8).map((base, pos) => {
const noise = (pos * 7 + 3) % 15;
const s = {
A: 5 + noise % 8, // 背景噪声:5~12
T: 5 + (noise+3) % 8,
C: 5 + (noise+5) % 8,
G: 5 + (noise+2) % 8,
};
s[base] = 85 + (pos * 3) % 12; // 正确碱基信号:85~96(远高于背景)
return { base, signals: s };
});
模拟 Illumina 荧光检测的信噪比特征:正确碱基信号强度约为背景的 8~12 倍,这是实际测序中 Q30 以上碱基的典型信噪比水平。
Simulates the signal-to-noise ratio characteristics of Illumina fluorescence detection: the correct base signal intensity is approximately 8–12 times the background, which is the typical SNR level for bases with Q30 or above in actual sequencing.
文件:js/app.jsFile: js/app.js
项目主程序,包含 SVG 渲染引擎、6个步骤的渲染函数、动画控制器和导航系统。
Main program of the project, containing the SVG rendering engine, rendering functions for all 6 steps, animation controller, and navigation system.
代码组织(按行号)Code Organization (by Line Number)
| 行范围Line Range | 模块Module | 内容Content |
|---|---|---|
| 1 – 47 | SVG 工具函数SVG Utility Functions | R/T/L/C/P/G/anim + drawBase + drawSeq + qChar |
| 48 – 337 | STEPS 数组定义STEPS Array Definition | 6个步骤的元数据(title/desc/substepLabels/theory/render引用)Metadata for 6 steps (title/desc/substepLabels/theory/render references) |
| 338 – 499 | renderStep1 | DNA片段化(4帧)DNA Fragmentation (4 frames) |
| 500 – 605 | renderStep2 | 文库构建(4帧)Library Prep (4 frames) |
| 606 – 700 | renderStep3 | SBS测序(6帧)SBS Sequencing (6 frames) |
| 701 – 800 | renderStep4 | 碱基识别与质控(4帧)Base Calling & QC (4 frames) |
| 801 – 886 | renderStep5 | 序列比对(5帧)Alignment (5 frames) |
| 887 – 980 | renderStep6 | 变异识别(4帧)Variant Calling (4 frames) |
| 981 – 1000 | 应用状态变量Application State Variables | curStep/curSub/isPlaying/playTimer |
| 1001 – 1031 | UI更新函数UI Update Functions | updateStepNav / updateSubstepDots / render / updateButtons |
| 1032 – 1049 | 初始化+事件绑定Initialization + Event Binding | DOMContentLoaded + 键盘快捷键Keyboard Shortcuts |
核心算法:SVG 渲染引擎Core Algorithm: SVG Rendering Engine
所有图形通过字符串拼接生成 SVG 标记,写入 <svg> 的 innerHTML。引擎由 7 个基础原语函数 + 2 个高阶函数组成。
All graphics are generated as SVG markup via string concatenation and written to the innerHTML of <svg>. The engine consists of 7 primitive functions + 2 higher-order functions.
基础原语函数Drawing Primitives
R(x, y, w, h, fill, rx=0, extra='') → string
→ '<rect x="100" y="50" width="80" height="20" fill="#3fb950" rx="4" />'
| 参数Parameter | 类型Type | 说明Description |
|---|---|---|
| x, y | number | 左上角坐标(SVG 坐标系)Top-left corner coordinates (SVG coordinate system) |
| w, h | number | 宽高(像素,基于 viewBox)Width and height (pixels, based on viewBox) |
| fill | string | 填充色(十六进制或 rgba)Fill color (hex or rgba) |
| rx | number | 圆角半径,默认 0Corner radius, default 0 |
| extra | string | 附加 SVG 属性,如 'stroke="#fff"'Additional SVG attributes, e.g. 'stroke="#fff"' |
T(x, y, s, fill, size, anchor, weight, family, extra) → string
→ '<text x="400" y="30" fill="#fff" font-size="14" text-anchor="middle" ...>Hello</text>'
text-anchor 控制水平对齐:'start'(左对齐)、'middle'(居中)、'end'(右对齐)。注意 SVG 文本 y 坐标是基线(baseline)位置,而非顶部,因此文本居中需要 y + fontSize/2 的视觉补偿。
text-anchor controls horizontal alignment: 'start' (left), 'middle' (center), 'end' (right). Note that SVG text y-coordinate is the baseline position, not the top, so centering text requires a visual offset of y + fontSize/2.
L(x1,y1,x2,y2, stroke, w, extra) → string
生成 <line>。常用 extra='stroke-dasharray="4,3"' 绘制虚线(用于断点标记、连接线等)。
Generates <line>. Commonly uses extra='stroke-dasharray="4,3"' to draw dashed lines (for breakpoints, connectors, etc.).
C / P / G / anim
| 函数Function | 生成元素Generated Element | 典型用途Typical Usage |
|---|---|---|
C(cx,cy,r,fill) | <circle> | 核苷酸粒子、进度圆点Nucleotide particles, progress dots |
P(d,fill,stroke,w) | <path> | 超声波波纹曲线Ultrasonic wave curves |
G(id,content,transform) | <g> | 元素分组(便于整体变换)Element grouping (for collective transforms) |
anim(id,attr,from,to,dur) | <animate> | SVG SMIL 动画(当前未启用)SVG SMIL animation (currently disabled) |
高阶函数:drawBase / drawSeqHigher-Order Functions: drawBase / drawSeq
drawBase(x, y, base, bw=17, bh=22, showLabel=true) → string
function drawBase(x, y, base, bw=17, bh=22, showLabel=true) {
const c = BASE_COLOR[base] || '#555'; // 未知碱基降级为灰色
let out = R(x, y, bw-1, bh, c, 2); // 宽度 -1 制造间距
if (showLabel) {
// 字号根据方块高度自适应
const fs = bh < 18 ? 8 : (bh < 24 ? 10 : 12);
// 文字在方块中垂直居中(baseline补偿 = fs/2 - 1)
out += T(x+bw/2-0.5, y+bh/2+fs/2-1, base, '#fff', fs, 'middle', 'bold');
}
return out;
}
字号自适应:方块高度 bh < 18 → 字号 8;18 ≤ bh < 24 → 字号 10;bh ≥ 24 → 字号 12。这使同一函数可用于小型 pileup 视图(bh=16)和大型演示视图(bh=30)。
Adaptive font size: block height bh < 18 → font 8; 18 ≤ bh < 24 → font 10; bh ≥ 24 → font 12. This allows the same function to be used for small pileup views (bh=16) and large demo views (bh=30).
drawSeq(x, y, seq, bw, bh, showLabels) → string
对序列字符串逐字符调用 drawBase,以 i*bw 的横向偏移排列,是一个纯粹的 map+join 操作。
Calls drawBase for each character in the sequence string, arranged with horizontal offset i*bw, a pure map+join operation.
qChar(q) → string
function qChar(q) { return String.fromCharCode(q + 33); }
实现 FASTQ Phred33 编码规范:Q值 + 33 = ASCII码。例如 Q30 → ASCII 63 → 字符 '?';Q40 → ASCII 73 → 字符 'I'。
Implements the FASTQ Phred33 encoding standard: Q value + 33 = ASCII code. For example, Q30 → ASCII 63 → character '?'; Q40 → ASCII 73 → character 'I'.
渲染算法:步骤1 DNA片段化Rendering Algorithm: Step 1 DNA Fragmentation
函数签名:renderStep1(substep: 0|1|2|3) → string
Function signature: renderStep1(substep: 0|1|2|3) → string
坐标系设计Coordinate System Design
// viewBox: 800×380,DNA居中绘制
const bpW = 15; // 每个碱基方块宽度
const totalW = REF.length * bpW; // 总宽度 = 39×15 = 585px
const sx = (W - totalW) / 2; // 水平居中起点 ≈ 107.5
const topY = H/2 - bpH - sGap; // 正链 Y 坐标
const botY = H/2 + sGap; // 互补链 Y 坐标
各帧逻辑Per-Frame Logic
| substep | 内容Content | 关键技术Key Technique |
|---|---|---|
| 0 | 完整双链DNA,显示5'→3'方向标注Complete double-stranded DNA with 5'→3' direction labels | 双 for 循环绘制正链+互补链,COMPLEMENT 对象计算互补碱基Two for-loops draw sense + complement strands, COMPLEMENT object computes complementary bases |
| 1 | 超声波 + 切割标记Ultrasonic waves + cut markers | SVG <path> 三次贝塞尔曲线模拟波浪;三角形箭头用 <polygon> 内联字符串SVG <path> cubic Bezier curves simulate waves; triangle arrows use inline <polygon> strings |
| 2 | DNA 断裂,片段有垂直位移DNA breaks apart, fragments have vertical displacement | fragDy=[-8,-3,3,8] 数组给4个片段不同的 dy 偏移,模拟散开效果fragDy=[-8,-3,3,8] array gives 4 fragments different dy offsets, simulating a spread effect |
| 3 | 4个独立片段横向排列,含坐标信息4 independent fragments arranged horizontally with coordinate info | 固定 Y 坐标数组 fragY=[70,140,210,280],每个片段一行Fixed Y coordinate array fragY=[70,140,210,280], one fragment per row |
[[0,9],[10,19],[20,28],[29,38]],分别约 10 bp,教学上简化了随机片段化的统计特性(实际产生的是正态分布的片段大小)。
Fragment division: Hard-coded as 4 fragments [[0,9],[10,19],[20,28],[29,38]], each approximately 10 bp, pedagogically simplifying the statistical properties of random fragmentation (which actually produces normally distributed fragment sizes).
渲染算法:步骤2 文库构建Rendering Algorithm: Step 2 Library Prep
函数签名:renderStep2(substep: 0|1|2|3) → string
Function signature: renderStep2(substep: 0|1|2|3) → string
以参考序列前10bp(REF.slice(0,10))为演示片段。互补链由 COMPLEMENT 映射计算。
Uses the first 10bp of the reference sequence (REF.slice(0,10)) as the demo fragment. The complementary strand is computed via the COMPLEMENT mapping.
| substep | 内容Content | 关键技术Key Technique |
|---|---|---|
| 0 | 原始片段双链 + 粗糙末端标注Raw fragment double strand + ragged end labels | 标注"粗糙末端"警告,说明超声剪切的末端不整齐Labels "ragged ends" warning, explaining that ultrasonic shearing produces uneven ends |
| 1 | T4聚合酶框出现在两侧末端T4 polymerase box appears at both ends | 半透明矩形 + 标签模拟酶分子;rgba(56,139,253,0.2) 表示酶Semi-transparent rectangle + label simulates enzyme; rgba(56,139,253,0.2) represents the enzyme |
| 2 | A尾在正链3'末端和负链3'末端出现A-tail appears at sense strand 3' end and antisense strand 3' end | 在片段两侧各绘制一个额外的 A 碱基块,颜色高亮,带方向标注Draws an extra A base block on each side of the fragment, color-highlighted with direction labels |
| 3 | P5/P7 接头连接,展示完整文库片段结构P5/P7 adapter ligation, showing complete library fragment structure | 两种颜色区分 P5(紫)和 P7(蓝)接头;主体片段居中,接头在左右两侧Two colors distinguish P5 (purple) and P7 (blue) adapters; main fragment centered, adapters on both sides |
渲染算法:步骤3 SBS测序Rendering Algorithm: Step 3 SBS Sequencing
函数签名:renderStep3(substep: 0|1|2|3|4|5) → string
Function signature: renderStep3(substep: 0|1|2|3|4|5) → string
已合成碱基数量计算Synthesized Base Count Calculation
const numCycles = substep === 0
? 0
: Math.min(substep * 2, 8);
// substep 1 → 2个循环
// substep 2 → 4个循环
// substep 3 → 6个循环
// substep 4 → 8个循环(上限)
// substep 5 → 8个循环(显示完整结果)
每个 substep 增加 2 个循环,使动画节奏适中(5帧展示8个碱基的合成过程)。
Each substep adds 2 cycles, keeping the animation pace moderate (5 frames to show the synthesis of 8 bases).
模板与合成链关系Template and Growing Chain Relationship
模板链(5'→3'): A T C G A A C G T T A C
互补链(3'→5'): T A G C T T G C A A T G
↑每次掺入互补碱基,从左到右逐步显示
聚合酶位置逻辑Polymerase Position Logic
聚合酶(🧬 图标 + 半透明矩形)始终显示在合成链当前末端的下一个位置:px = startX + numCycles * bpW,当 numCycles === 8 时隐藏(合成完毕)。
The polymerase (🧬 icon + semi-transparent rectangle) is always shown at the next position after the current end of the growing chain: px = startX + numCycles * bpW, hidden when numCycles === 8 (synthesis complete).
信号条形图Signal Bar Chart
在合成链下方绘制已完成循环的信号条:每个位置取 SBS_GROWING[i] 对应的颜色,绘制一个高 24px 的彩色矩形,直观表示"相机检测到的颜色→碱基"的对应关系。
Signal bars for completed cycles are drawn below the growing chain: each position uses the color corresponding to SBS_GROWING[i], drawing a 24px-tall colored rectangle that visually represents the "camera-detected color → base" correspondence.
渲染算法:步骤4 碱基识别与质控Rendering Algorithm: Step 4 Base Calling & QC
函数签名:renderStep4(substep: 0|1|2|3) → string
Function signature: renderStep4(substep: 0|1|2|3) → string
| substep | 关键渲染逻辑Key Rendering Logic |
|---|---|
| 0 · 荧光信号0 · Fluorescence Signal | 对每个位置(8个),并排绘制4根颜色柱(A/T/C/G),高度 = signals[base] / 100 × barMaxH。所有4色同时显示,模拟实际荧光成像的多通道数据。For each position (8 total), draws 4 color bars side by side (A/T/C/G), height = signals[base] / 100 × barMaxH. All 4 colors displayed simultaneously, simulating multi-channel fluorescence imaging data. |
| 1 · 碱基识别1 · Base Calling | 重绘信号柱,正确碱基的柱不透明(opacity=1),其余3柱加 '55' 透明度后缀变暗;在每个位置下方额外绘制识别到的碱基方块,并用虚线箭头连接"信号→碱基"。Redraws signal bars; the correct base bar is opaque (opacity=1), the other 3 bars are dimmed with '55' transparency suffix; draws identified base blocks below each position, connected by dashed arrows from "signal → base". |
| 2 · Q值计算2 · Q Value Calculation | 绘制上方碱基行 + 下方Q值柱状图。柱高 = Q / 40 × qbarMaxH。颜色按阈值分级:Q≥30绿色、Q20~30黄色、Q<20红色,直观反映质量分布。Draws base row above + Q value bar chart below. Bar height = Q / 40 × qbarMaxH. Color graded by threshold: Q≥30 green, Q20–30 yellow, Q<20 red, visually reflecting quality distribution. |
| 3 · FASTQ格式3 · FASTQ Format | 静态文本展示4行FASTQ记录,用不同颜色区分各行,附右侧字段说明。质量字符通过 qChar() 函数从Q值转换为ASCII字符。Static text displays a 4-line FASTQ record with different colors for each line, with field descriptions on the right. Quality characters are converted from Q values to ASCII via the qChar() function. |
渲染算法:步骤5 序列比对Rendering Algorithm: Step 5 Alignment
函数签名:renderStep5(substep: 0|1|2|3|4) → string
Function signature: renderStep5(substep: 0|1|2|3|4) → string
可见reads计算Visible Reads Calculation
const visibleReads =
substep === 0 ? 0 :
substep === 1 ? 3 :
substep === 2 ? 6 : 9; // substep 3和4都显示全部9条
每帧递增显示 3 条 reads,模拟比对算法逐步将 reads 定位到参考上的过程。
Each frame incrementally displays 3 reads, simulating the alignment algorithm gradually mapping reads to the reference.
错配检测与高亮Mismatch Detection and Highlighting
for (let j = 0; j < read.len; j++) {
const globalPos = read.start + j;
const base = read.seq[j];
const refBase = REF[globalPos];
const isMismatch = (base !== refBase);
// 错配碱基:降低透明度 + 红色边框
R(x, ry2, bpW-1, bpH, BASE_COLOR[base] + (isMismatch ? '' : '77'), 2,
isMismatch ? 'stroke="#f85149" stroke-width="1.2"' : '');
}
正常碱基颜色加 '77' 十六进制透明度(约47%)降低视觉权重,错配碱基保持全色并添加红色边框,引导注意力。
Normal bases have '77' hex transparency (about 47%) added to reduce visual weight, while mismatched bases retain full color and get a red border to draw attention.
SNP 位置高亮(substep 4)SNP Position Highlighting (substep 4)
在 substep 4 额外绘制一个纵向半透明矩形覆盖 SNP 列(从参考序列到所有 reads),颜色为 rgba(248,81,73,0.08)(极低透明度红色),配合虚线边框形成视觉焦点,并在底部显示等位基因统计数。
In substep 4, an additional vertical semi-transparent rectangle is drawn covering the SNP column (from reference to all reads), colored rgba(248,81,73,0.08) (very low transparency red), with a dashed border to create visual focus, and allele counts displayed at the bottom.
渲染算法:步骤6 变异识别Rendering Algorithm: Step 6 Variant Calling
函数签名:renderStep6(substep: 0|1|2|3) → string
Function signature: renderStep6(substep: 0|1|2|3) → string
SNP 位点数据汇总(所有帧共用)SNP Site Data Summary (Shared Across All Frames)
const snpBases = [];
READS.forEach(r => {
// 判断 read 是否覆盖 SNP 位置
if (r.start <= SNP_POS && SNP_POS < r.start + r.len) {
snpBases.push({
readId: r.id,
base: r.seq[SNP_POS - r.start] // 绝对坐标 → 相对坐标
});
}
});
// 结果:5条reads覆盖,其中G=2,A=3
| substep | 关键逻辑Key Logic |
|---|---|
| 0 · 聚焦Pileup0 · Focused Pileup | 以 SNP_POS 为中心,显示前后各5个碱基的上下文序列(参考序列);SNP位置用白色 2px 边框高亮。覆盖该位点的5条reads竖向堆叠显示,错配碱基红色高亮。Centered on SNP_POS, displays 5 bases of context on each side (reference sequence); SNP position highlighted with white 2px border. The 5 reads covering this site are stacked vertically with mismatched bases highlighted in red. |
| 1 · 碱基计数1 · Base Counting | 在左侧显示各read的碱基,右侧绘制G/A的等高柱状图;柱高 = count/total × 120px;底部显示变异频率(VAF)= aCount/total,格式化为百分比。Displays each read's base on the left, draws G/A bar chart on the right; bar height = count/total × 120px; shows variant allele frequency (VAF) = aCount/total at the bottom, formatted as percentage. |
| 2 · 贝叶斯模型2 · Bayesian Model | 三种基因型(0/0, 0/1, 1/1)的后验概率以固定值硬编码演示(0.05, 0.92, 0.03);概率值映射为横向条形图宽度(width = prob × 400px),让学习者直观感受贝叶斯输出。Posterior probabilities for three genotypes (0/0, 0/1, 1/1) are hard-coded for demonstration (0.05, 0.92, 0.03); probability values map to horizontal bar widths (width = prob × 400px), giving learners an intuitive feel for Bayesian output. |
| 3 · VCF输出3 · VCF Output | 静态渲染4行VCF记录(含header行),数据行用绿色高亮;最后的总结框用 rgba(63,185,80,0.08) 绿色背景 + 边框表示"通过"。Statically renders 4-line VCF record (including header), data row highlighted in green; final summary box uses rgba(63,185,80,0.08) green background + border to indicate "pass". |
核心算法:动画状态机Core Algorithm: Animation State Machine
播放系统是一个基于 setTimeout 递归调用的简单状态机:
The playback system is a simple state machine based on recursive setTimeout calls:
function advanceFrame() {
if (!isPlaying) return; // 守卫:已暂停则退出
const maxSub = STEPS[curStep].substepLabels.length - 1;
if (curSub < maxSub) {
// 同一步骤内前进一帧
curSub++;
render();
playTimer = setTimeout(advanceFrame, PLAY_INTERVAL); // 2200ms后继续
} else if (curStep < STEPS.length - 1) {
// 当前步骤最后一帧 → 跳到下一步骤第一帧
curStep++; curSub = 0;
render();
playTimer = setTimeout(advanceFrame, PLAY_INTERVAL);
} else {
// 所有步骤全部播放完毕 → 自然停止
pausePlay();
}
}
pausePlay 的重要性Importance of pausePlay
function pausePlay() {
isPlaying = false;
clearTimeout(playTimer); // ← 关键:取消待执行的回调
updateButtons();
}
clearTimeout,即使 isPlaying=false,已排队的回调仍会在 2200ms 后触发一次,导致在手动操作后出现意外跳帧。因此所有导航函数(prevStep、nextStep、subPrev、subNext、goToStep)都首先调用 pausePlay()。
Without calling clearTimeout, even if isPlaying=false, the queued callback will still fire once after 2200ms, causing unexpected frame jumps after manual operations. Therefore all navigation functions (prevStep, nextStep, subPrev, subNext, goToStep) call pausePlay() first.
数据结构速查Data Structure Reference
ReadObj(读段对象)ReadObj (Read Object)
id: number, // 读段唯一ID,1~9Unique read ID, 1–9
start: number, // 在参考基因组上的起始位置(0-based)Start position on reference genome (0-based)
seq: string, // 碱基序列字符串(可能含突变)Base sequence string (may contain mutations)
qual: number[], // 每个碱基的 Phred Q 值,范围 18~40Phred Q value per base, range 18–40
len: number, // 序列长度(= seq.length)Sequence length (= seq.length)
muts: [number,string][] // 突变列表,每项为 [相对位置, 替代碱基]Mutation list, each item is [relative position, alternate base]
}
StepObj(步骤配置对象)StepObj (Step Configuration Object)
title: string, // 步骤标题(显示在 #step-title)Step title (displayed in #step-title)
desc: string, // 步骤简介(显示在 #step-desc)Step description (displayed in #step-desc)
substepLabels: string[], // 每一帧的文字说明,length决定帧数Text description per frame, length determines frame count
theory: string, // HTML字符串,注入 #theory-contentHTML string, injected into #theory-content
render: (substep) => string // 渲染函数,返回SVG内容字符串Rendering function, returns SVG content string
}
SignalDataObj(荧光信号对象)SignalDataObj (Fluorescence Signal Object)
base: string, // 正确碱基 ('T'|'A'|'G'|'C')Correct base ('T'|'A'|'G'|'C')
signals: {
A: number, // A通道强度(5~96)A channel intensity (5–96)
T: number, // T通道强度T channel intensity
C: number, // C通道强度C channel intensity
G: number // G通道强度(正确碱基为85~96)G channel intensity (85–96 for correct base)
}
}
应用状态(全局变量)Application State (Global Variables)
| 变量Variable | 类型Type | 范围/说明Range/Description |
|---|---|---|
curStep | number | [0, 5],对应 STEPS 数组索引[0, 5], corresponds to STEPS array index |
curSub | number | [0, STEPS[curStep].substepLabels.length-1] |
isPlaying | boolean | true=自动播放中,false=暂停/手动true=auto-playing, false=paused/manual |
playTimer | number|null | setTimeout 返回的定时器 ID,null 表示无待执行回调Timer ID returned by setTimeout, null means no pending callback |
键盘快捷键Keyboard Shortcuts
| 按键Key | 功能Function | 备注Notes |
|---|---|---|
| → | 前进一帧Advance one frame | 到步骤末尾时自动跳下一步骤Auto-jumps to next step at end of current step |
| ← | 回退一帧Go back one frame | 到步骤开头时自动跳上一步骤末尾Auto-jumps to end of previous step at beginning of current step |
| Space | 播放/暂停Play/Pause | e.preventDefault() 阻止页面滚动e.preventDefault() prevents page scrolling |
扩展指南Extension Guide
新增一个可视化步骤Adding a New Visualization Step
只需在 js/app.js 的 STEPS 数组中追加一个配置对象:
Simply append a configuration object to the STEPS array in js/app.js:
// 在 STEPS 数组末尾添加:
{
title: '步骤 7:基因注释(Gene Annotation)',
desc: '将识别到的变异与基因功能数据库比对',
substepLabels: [
'加载基因组注释数据库',
'变异位点落入基因区域',
'功能预测完成'
],
theory: `<div class="tsec"><h4>...</h4><p>...</p></div>`,
render: renderStep7 // 新建对应渲染函数
}
然后实现 renderStep7(substep) 函数,返回 SVG 字符串即可。无需修改任何其他代码,导航系统自动适配。
Then implement the renderStep7(substep) function to return an SVG string. No other code changes needed; the navigation system adapts automatically.
修改播放速度Changing Playback Speed
// app.js 顶部的常量:
const PLAY_INTERVAL = 2200; // 改为 1000 可加快,3000 可放慢(单位ms)
修改 Mock 数据Modifying Mock Data
js/data.js 中,修改 REF 字符串、SNP_POS 或 makeRead() 调用参数即可更换演示数据集,无需触碰渲染逻辑。
All data is centralized in js/data.js. Modify the REF string, SNP_POS, or makeRead() call parameters to change the demo dataset without touching rendering logic.
添加连续动画Adding Continuous Animations
当前系统使用离散帧模型,如需在两帧之间添加 CSS 过渡动画:
The current system uses a discrete frame model. To add CSS transition animations between frames:
/* 在 style.css 中为 SVG 容器添加 */
#vis-area svg {
transition: opacity 0.3s ease;
}
/* 在 app.js render() 中触发:*/
svg.style.opacity = '0';
setTimeout(() => {
svg.innerHTML = step.render(curSub);
svg.style.opacity = '1';
}, 150);
添加数据说明文档链接(本文档的集成方式)Adding Documentation Link (How This Document Is Integrated)
本文档通过在 index.html 的 header 区域添加 <a> 标签链接实现集成,无需任何路由框架:
This document is integrated by adding an <a> tag link in the header area of index.html, without any routing framework:
<!-- index.html header 内 -->
<a href="docs.html" class="doc-btn">📄 技术文档</a>
docs.html 作为独立页面,不依赖 app.js,可独立访问和打印。
docs.html is a standalone page, independent of app.js, and can be accessed and printed independently.