/ concept-collection / timeseries-compressibility
Sign in
concept-collection / timeseries-compressibility
timeseries-compressibility / src / components / ScrollingView.tsx
152 lines · 5.0 KBCodeBlameHistory
eb36f3fMake the signal view stationary by default with a play toggleJeremy Magland 1import { useEffect, useRef, useState } from 'react'
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 2import { LatentSource, LATENT_SEED } from '../model/latent'
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 4/** Display samples generated per second while playing; px per sample fixed. */
6const PX_PER_SAMPLE = 2
8/** A nice round gridline step ≤ span/2. */
9function niceStep(span: number): number {
10 const raw = span / 2
11 const mag = 10 ** Math.floor(Math.log10(raw))
12 for (const m of [5, 2.5, 2, 1]) if (m * mag <= raw) return m * mag
13 return mag
16/**
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 17 * A window of the quantized signal z, drawn as connected line segments.
18 * The underlying randomness is a fixed latent sequence indexed by absolute
19 * sample position — parameter changes re-render the same window of latent
20 * data (no resampling), so the trace morphs smoothly. Stationary by default;
21 * the play toggle advances the window through the latent sequence.
23export default function ScrollingView(props: {
24 kernel: Float64Array
25 sigma: number
26 /** Predicted std of the quantized signal, for a stable y-scale. */
27 sigmaY: number
28}) {
29 const canvasRef = useRef<HTMLCanvasElement>(null)
eb36f3fMake the signal view stationary by default with a play toggleJeremy Magland 30 const [playing, setPlaying] = useState(false)
31 const playingRef = useRef(playing)
32 playingRef.current = playing
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 33 // Latent noise and window position survive parameter changes.
34 const latentRef = useRef<LatentSource | null>(null)
35 if (!latentRef.current) latentRef.current = new LatentSource(LATENT_SEED)
36 const posRef = useRef(0)
38 useEffect(() => {
39 const canvas = canvasRef.current
40 if (!canvas) return
41 const ctx = canvas.getContext('2d')
42 if (!ctx) return
45 const scale = Math.max(4 * props.sigmaY, 3.5)
46 const gridStep = niceStep(scale)
48 let styles = getComputedStyle(canvas)
49 const scheme = window.matchMedia('(prefers-color-scheme: dark)')
50 const refreshStyles = () => {
51 styles = getComputedStyle(canvas)
52 }
53 scheme.addEventListener('change', refreshStyles)
55 let raf = 0
56 let last = performance.now()
57 let carry = 0
59 const draw = (now: number) => {
60 raf = requestAnimationFrame(draw)
61 const dt = Math.min(0.25, (now - last) / 1000)
62 last = now
eb36f3fMake the signal view stationary by default with a play toggleJeremy Magland 63 if (playingRef.current) {
64 carry += dt * RATE
65 const n = Math.floor(carry)
66 carry -= n
69 carry = 0
70 }
72 const dpr = window.devicePixelRatio || 1
73 const w = canvas.clientWidth
74 const h = canvas.clientHeight
75 if (w === 0 || h === 0) return
76 if (canvas.width !== Math.round(w * dpr) || canvas.height !== Math.round(h * dpr)) {
77 canvas.width = Math.round(w * dpr)
78 canvas.height = Math.round(h * dpr)
79 }
80 ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 82 const visible = Math.floor(w / PX_PER_SAMPLE)
83 // The window ends at posRef and never reaches before index 0, so the
84 // first thing shown is the start of the compression block.
85 if (posRef.current < visible) posRef.current = visible
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 86 const win = latent.window(posRef.current - visible, visible, props.kernel, props.sigma)
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 88 const surface = styles.getPropertyValue('--surface')
89 ctx.fillStyle = surface
90 ctx.fillRect(0, 0, w, h)
92 const yOf = (v: number) => h / 2 - (v / scale) * (h / 2 - 12)
94 ctx.lineWidth = 1
95 ctx.font = '11px system-ui, sans-serif'
96 ctx.textAlign = 'left'
97 for (let g = -2; g <= 2; g++) {
98 const v = g * gridStep
99 if (Math.abs(v) > scale) continue
100 const y = Math.round(yOf(v)) + 0.5
102 ctx.strokeStyle = styles.getPropertyValue('--grid')
103 ctx.beginPath()
104 ctx.moveTo(0, y)
105 ctx.lineTo(w, y)
106 ctx.stroke()
107 }
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 108 // A surface-colored halo keeps the label readable over the trace.
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 109 const label = `${v > 0 ? '+' : ''}${+v.toPrecision(3)}`
110 ctx.fillStyle = styles.getPropertyValue('--muted')
112 ctx.lineWidth = 3
113 ctx.strokeText(label, 6, y - 4)
114 ctx.fillText(label, 6, y - 4)
115 ctx.lineWidth = 1
116 }
117 const zeroY = Math.round(yOf(0)) + 0.5
118 ctx.strokeStyle = styles.getPropertyValue('--baseline')
119 ctx.beginPath()
120 ctx.moveTo(0, zeroY)
121 ctx.lineTo(w, zeroY)
122 ctx.stroke()
124 ctx.strokeStyle = styles.getPropertyValue('--series-1')
125 ctx.lineWidth = 2
126 ctx.lineJoin = 'round'
127 ctx.beginPath()
128 for (let i = 0; i < visible; i++) {
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 129 const x = w - (visible - i) * PX_PER_SAMPLE + PX_PER_SAMPLE / 2
130 const y = yOf(win[i])
132 else ctx.lineTo(x, y)
133 }
134 ctx.stroke()
135 }
136 raf = requestAnimationFrame(draw)
138 return () => {
139 cancelAnimationFrame(raf)
140 scheme.removeEventListener('change', refreshStyles)
141 }
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 142 }, [props.kernel, props.sigma, props.sigmaY])
145 <div className="scroll-wrap">
146 <canvas ref={canvasRef} className="scroll-canvas" />
147 <button className="play-btn" onClick={() => setPlaying(p => !p)}>
148 {playing ? '⏸ pause' : '▶ play'}
149 </button>
150 </div>
151 )
moveopenescclose