// ========================================================================= // §7, Tokenizer (1-mer vs 6-mer) // ========================================================================= (function initDemo7() { const els = { input: document.getElementById("d7-input"), len: document.getElementById("d7-len"), oneSeq: document.getElementById("d7-1mer"), sixSeq: document.getElementById("d7-6mer"), oneTok: document.getElementById("d7-1mer-tok"), sixTok: document.getElementById("d7-6mer-tok"), oneAtt: document.getElementById("d7-1mer-att"), sixAtt: document.getElementById("d7-6mer-att"), bars: document.getElementById("d7-bars"), speedup: document.getElementById("d7-speedup"), }; // 8-color palette for 6-mer tokens (cycle); 1-mer uses base coloring. // A modern jewel-tone set: teal / violet / sky / pink / slate / amber / // green / purple. Roughly matched saturation + lightness so adjacent chips // read as siblings rather than a yellow-pages rainbow. const TOKEN_PALETTE = [ "#0f766e", "#7c3aed", "#0369a1", "#be185d", "#475569", "#a16207", "#15803d", "#9333ea", ]; // 1-mer bases follow the conventional sequence-viewer mapping (A green, // C blue, G amber, T pink) but in the same darker register so they sit // next to the 6-mer chips without clashing. const BASE_FILL = { A: "#15803d", C: "#0369a1", G: "#a16207", T: "#be185d", N: "#64748b", }; function clean(s) { return (s || "").toUpperCase().replace(/[^ACGTN]/g, ""); } function render() { const seq = clean(els.input.value); els.len.textContent = `${seq.length} bp`; // 1-mer: each base its own pill let one = ""; for (let i = 0; i < seq.length; i++) { const b = seq[i]; one += `${b}`; } els.oneSeq.classList.toggle("empty", seq.length === 0); els.oneSeq.innerHTML = seq.length ? one : "·"; // 6-mer: chunks of 6, alternating palette colors let six = ""; for (let i = 0; i < seq.length; i += 6) { const chunk = seq.slice(i, i + 6); const isFull = chunk.length === 6; const c = TOKEN_PALETTE[(i / 6) % TOKEN_PALETTE.length]; const padded = isFull ? chunk : chunk + "•".repeat(6 - chunk.length); six += `${padded}`; } els.sixSeq.classList.toggle("empty", seq.length === 0); els.sixSeq.innerHTML = seq.length ? six : "·"; const n1 = seq.length; const n6 = Math.ceil(seq.length / 6); els.oneTok.textContent = n1.toLocaleString("en-US"); els.sixTok.textContent = n6.toLocaleString("en-US"); els.oneAtt.innerHTML = `${(n1*n1).toLocaleString("en-US")}`; els.sixAtt.innerHTML = `${(n6*n6).toLocaleString("en-US")}`; // Speedup bars: visualize attention cost ratio const maxCost = n1 * n1 || 1; const W = 1000, H = 70, padL = 110, padR = 80, rowH = 18, padT = 12; let svg = ""; const rows = [ { label: "1-mer", n: n1, cost: n1 * n1, color: "#888" }, { label: "6-mer", n: n6, cost: n6 * n6, color: "#317f3f" }, ]; rows.forEach((r, i) => { const y = padT + i * (rowH + 8); svg += `${r.label}`; const w = (r.cost / maxCost) * (W - padL - padR); svg += ``; svg += `${r.cost.toLocaleString("en-US")}`; }); els.bars.setAttribute("viewBox", `0 0 ${W} ${H}`); els.bars.style.height = `${H}px`; els.bars.innerHTML = svg; const ratio = n1 > 0 ? (n1 * n1) / Math.max(1, n6 * n6) : 36; els.speedup.textContent = `${ratio.toFixed(1)}× cheaper attention`; } els.input.addEventListener("input", render); render(); })();