|
|
| <style> |
| .wave-wrap { font-family: var(--font-sans); padding: 0.5rem 0 1rem; } |
| .wave-controls { display: flex; gap: 20px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; } |
| .wave-label { font-size: 12px; color: var(--color-text-secondary); min-width: 120px; } |
| .wave-val { font-size: 12px; font-weight: 500; color: var(--color-text-primary); min-width: 28px; } |
| .panel-row { display: flex; gap: 12px; } |
| .panel { flex: 1; background: var(--color-background-secondary); border-radius: 8px; padding: 10px 12px; } |
| .panel-head { font-size: 12px; font-weight: 500; color: var(--color-text-primary); margin-bottom: 6px; } |
| .panel-note { font-size: 11px; color: var(--color-text-secondary); margin-top: 4px; } |
| </style> |
| <div class="wave-wrap"> |
| <div class="wave-controls"> |
| <div style="display:flex;align-items:center;gap:8px"> |
| <span class="wave-label" style="color:#1D9E75;font-weight:500">pos (word index)</span> |
| <input type="range" min="0" max="49" value="10" id="sl-pos" style="width:120px" oninput="update()"> |
| <span class="wave-val" id="lbl-pos">10</span> |
| </div> |
| <div style="display:flex;align-items:center;gap:8px"> |
| <span class="wave-label" style="color:#BA7517;font-weight:500">highlight dim i</span> |
| <input type="range" min="0" max="255" value="0" id="sl-dim" style="width:120px" oninput="update()"> |
| <span class="wave-val" id="lbl-dim">0</span> |
| </div> |
| </div> |
|
|
| <div class="panel-row" style="margin-bottom:10px"> |
| <div class="panel"> |
| <div class="panel-head">Looking <em>across</em> one row → (horizontal slice at selected pos)</div> |
| <canvas id="hcv" style="width:100%;display:block"></canvas> |
| <div class="panel-note" id="hnote"></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-head">Looking <em>down</em> one column ↓ (vertical slice at selected dim i)</div> |
| <canvas id="vcv" style="width:100%;display:block"></canvas> |
| <div class="panel-note" id="vnote"></div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const ROWS=50, COLS=256, D=512; |
| function pe(pos,i){ |
| const d=Math.pow(10000,(2*Math.floor(i/2))/D); |
| return (i%2===0)?Math.sin(pos/d):Math.cos(pos/d); |
| } |
| |
| const isDark = matchMedia('(prefers-color-scheme: dark)').matches; |
| const teal = '#1D9E75', amber = '#BA7517'; |
| const textCol = isDark ? 'rgba(200,198,190,0.9)' : 'rgba(60,60,58,0.9)'; |
| const gridCol = isDark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.07)'; |
| |
| function drawLine(cvs, data, color, hlIdx, xLabel, showFreq) { |
| const dpr = window.devicePixelRatio||1; |
| const w = cvs.offsetWidth||300; |
| const h = 110; |
| cvs.width = w*dpr; cvs.height = h*dpr; |
| cvs.style.height = h+'px'; |
| const ctx = cvs.getContext('2d'); |
| ctx.scale(dpr, dpr); |
| |
| const pad = {l:6, r:6, t:10, b:20}; |
| const pw = w - pad.l - pad.r; |
| const ph = h - pad.t - pad.b; |
| const n = data.length; |
| |
| ctx.clearRect(0,0,w,h); |
| |
| |
| ctx.strokeStyle = gridCol; ctx.lineWidth = 0.5; |
| [0.25, 0.5, 0.75].forEach(f => { |
| const y = pad.t + f*ph; |
| ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l+pw, y); ctx.stroke(); |
| }); |
| |
| ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.18)'; |
| ctx.lineWidth = 1; |
| ctx.beginPath(); ctx.moveTo(pad.l, pad.t+ph/2); ctx.lineTo(pad.l+pw, pad.t+ph/2); ctx.stroke(); |
| |
| |
| ctx.strokeStyle = color; ctx.lineWidth = 1.5; ctx.lineJoin = 'round'; |
| ctx.beginPath(); |
| data.forEach((v, i) => { |
| const x = pad.l + (i/(n-1))*pw; |
| const y = pad.t + (1-(v+1)/2)*ph; |
| i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y); |
| }); |
| ctx.stroke(); |
| |
| |
| if (hlIdx >= 0 && hlIdx < n) { |
| const x = pad.l + (hlIdx/(n-1))*pw; |
| const y = pad.t + (1-(data[hlIdx]+1)/2)*ph; |
| ctx.fillStyle = amber; |
| ctx.beginPath(); ctx.arc(x,y,4,0,Math.PI*2); ctx.fill(); |
| |
| ctx.fillStyle = amber; |
| ctx.font = `500 11px var(--font-sans)`; |
| ctx.textAlign = 'center'; |
| ctx.fillText(data[hlIdx].toFixed(3), Math.min(Math.max(x,30),pw-20), y > pad.t+12 ? y-7 : y+14); |
| } |
| |
| |
| ctx.fillStyle = textCol; ctx.font = `10px var(--font-sans)`; |
| ctx.textAlign = 'center'; |
| [0, Math.floor(n/2), n-1].forEach(i => { |
| const x = pad.l + (i/(n-1))*pw; |
| ctx.fillText(xLabel(i), x, h-4); |
| }); |
| |
| |
| ctx.textAlign = 'right'; |
| ctx.fillText('+1', pad.l-1, pad.t+5); |
| ctx.fillText('−1', pad.l-1, pad.t+ph+4); |
| } |
| |
| function update() { |
| const pos = +document.getElementById('sl-pos').value; |
| const dim = +document.getElementById('sl-dim').value; |
| document.getElementById('lbl-pos').textContent = pos; |
| document.getElementById('lbl-dim').textContent = dim; |
| |
| |
| const hdata = Array.from({length:COLS}, (_,i) => pe(pos,i)); |
| drawLine(document.getElementById('hcv'), hdata, teal, dim, i => i===0?'i=0':i===127?'128':i===255?'255':'', true); |
| |
| |
| const vdata = Array.from({length:ROWS}, (_,r) => pe(r,dim)); |
| drawLine(document.getElementById('vcv'), vdata, amber, pos, r => r===0?'pos=0':r===24?'24':r===49?'49':'', false); |
| |
| |
| const wl = (2*Math.PI*Math.pow(10000, dim/D)).toFixed(0); |
| const speed = dim < 20 ? '⚡ very fast' : dim < 80 ? '⚡ fast' : dim < 160 ? '~ moderate' : '🐢 slow'; |
| document.getElementById('hnote').innerHTML = `Left = high-frequency oscillations. Right = nearly flat. Highlighted dot at <b>i=${dim}</b> = ${hdata[dim].toFixed(3)}.`; |
| document.getElementById('vnote').innerHTML = `Dimension i=${dim} (${speed}, wavelength≈${wl}). Highlighted dot at <b>pos=${pos}</b> = ${vdata[pos].toFixed(3)}.`; |
| } |
| |
| setTimeout(update, 60); |
| window.addEventListener('resize', update); |
| </script> |
|
|