File size: 4,384 Bytes
39a61da
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ea40ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
 
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
5ea40ce
39a61da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// =========================================================================
// §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 += `<span style="display:inline-block;background:${BASE_FILL[b]||"#888"};color:#fff;padding:2px 4px;margin:1px;border-radius:2px;font-size:10px;letter-spacing:0">${b}</span>`;
    }
    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 += `<span style="display:inline-block;background:${c};color:#fff;padding:3px 7px;margin:2px;border-radius:3px;font-size:11px;letter-spacing:1px;${isFull?"":"opacity:0.55"}">${padded}</span>`;
    }
    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")}<span style="color:#999;font-size:9px;margin-left:3px">L²</span>`;
    els.sixAtt.innerHTML = `${(n6*n6).toLocaleString("en-US")}<span style="color:#999;font-size:9px;margin-left:3px">L²</span>`;

    // 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 += `<text x="${padL - 8}" y="${y + 13}" font-family="JetBrains Mono" font-size="11" fill="#333" text-anchor="end">${r.label}</text>`;
      const w = (r.cost / maxCost) * (W - padL - padR);
      svg += `<rect x="${padL}" y="${y}" width="${Math.max(2, w)}" height="${rowH}" fill="${r.color}"/>`;
      svg += `<text x="${padL + w + 6}" y="${y + 13}" font-family="JetBrains Mono" font-size="10" fill="#333">${r.cost.toLocaleString("en-US")}</text>`;
    });
    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();
})();