36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 1import { useRef, useState } from 'react'
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 2import type { BoundResult, CodecResult } from '../compress/codecs'
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 3import { useWidth } from './useWidth'
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 5const GROUPS = ['no prefilter', 'delta', 'LPC']
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 6const CODER_NAMES = ['zlib', 'zstd', 'ANS']
7const CODER_VARS = ['var(--series-1)', 'var(--series-2)', 'var(--series-3)']
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 8const BOUND_LABEL = 'entropy limit'
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 9
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 10const LABEL_W = 100
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 11const RIGHT_PAD = 64
12const AXIS_H = 26
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 13const GROUP_H = 20
14const ROW_H = 24
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 15const BAR_H = 16
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 16const ROWS_PER_GROUP = 4
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 17
18type Metric = 'bits' | 'ratio'
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 20/** One drawn bar: a measured codec size, or the entropy limit for its group. */
21interface Row {
22 key: string
23 label: string
24 name: string
25 note: string
26 bytes: number
27 bitsPerSample: number
28 ratio: number
29 isBound: boolean
30 color: string
31}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 33interface Tip {
34 x: number
35 y: number
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 37}
39function axisTicks(max: number): number[] {
40 const step = max > 24 ? 8 : max > 12 ? 4 : max > 6 ? 2 : max > 3 ? 1 : 0.5
41 const out: number[] = []
42 for (let v = 0; v <= max + 1e-9; v += step) out.push(v)
43 return out
44}
46/** A bar whose data-end is rounded (4px) while the baseline end stays square. */
47function barPath(x0: number, y: number, len: number, h: number): string {
48 const r = Math.min(4, len)
49 return `M${x0},${y} h${len - r} a${r},${r} 0 0 1 ${r},${r} v${h - 2 * r} a${r},${r} 0 0 1 ${-r},${r} h${-(len - r)} z`
50}
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 52/** Codec rows then the entropy limit, group by group. */
53function buildRows(results: CodecResult[], bounds: BoundResult[]): Row[] {
54 const rows: Row[] = []
55 GROUPS.forEach((group, g) => {
56 CODER_NAMES.forEach((coder, c) => {
57 const r = results[g * 3 + c]
58 if (!r) return
59 rows.push({
60 key: r.codec,
61 label: coder,
62 name: r.codec,
63 note: r.note,
64 bytes: r.bytes,
65 bitsPerSample: r.bitsPerSample,
66 ratio: r.ratio,
67 isBound: false,
68 color: CODER_VARS[c],
69 })
70 })
71 const b = bounds.find(x => x.group === group)
72 if (b) {
73 rows.push({
74 key: `${group}-bound`,
75 label: BOUND_LABEL,
76 name: `${BOUND_LABEL} (${group})`,
77 note: b.note,
78 bytes: b.bytes,
79 bitsPerSample: b.bitsPerSample,
80 ratio: b.ratio,
81 isBound: true,
82 color: 'var(--muted)',
83 })
84 }
85 })
86 return rows
87}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 89export default function CompressionChart(props: {
90 results: CodecResult[]
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 91 bounds: BoundResult[]
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 92 /** The browser-estimated entropy rate R, once at least one past is in. */
93 rateBits: number | null
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 94 computing: boolean
95}) {
96 const ref = useRef<HTMLDivElement>(null)
97 const width = useWidth(ref, 720)
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 98 const [metric, setMetric] = useState<Metric>('ratio')
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 99 const [tip, setTip] = useState<Tip | null>(null)
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 100 const [hovered, setHovered] = useState<string | null>(null)
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 101
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 102 const { results, bounds, rateBits } = props
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 103 if (results.length === 0) {
104 return <p className="card-note">Computing compression on the first block…</p>
105 }
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 107 const rows = buildRows(results, bounds)
108 const value = (r: { bitsPerSample: number; ratio: number }) =>
109 metric === 'bits' ? r.bitsPerSample : r.ratio
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 110 const rateValue =
111 rateBits !== null && rateBits > 0 ? (metric === 'bits' ? rateBits : 16 / rateBits) : null
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 112 const xMax =
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 114 ? Math.max(16, ...rows.map(value), rateValue ?? 0) * 1.02
115 : Math.max(...rows.map(value), rateValue ?? 0) * 1.1
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 116
117 const plotW = width - LABEL_W - RIGHT_PAD
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 118 const height = AXIS_H + GROUPS.length * (GROUP_H + ROWS_PER_GROUP * ROW_H) + 6
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 119 const xOf = (v: number) => LABEL_W + (v / xMax) * plotW
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 120 const rowY = (i: number) =>
121 AXIS_H +
122 Math.floor(i / ROWS_PER_GROUP) * (GROUP_H + ROWS_PER_GROUP * ROW_H) +
123 GROUP_H +
124 (i % ROWS_PER_GROUP) * ROW_H
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 125
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 126 const fmt = (r: Row) => (metric === 'bits' ? r.bitsPerSample.toFixed(2) : `${r.ratio.toFixed(2)}×`)
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 127
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 128 const onBarMove = (e: React.PointerEvent, row: Row) => {
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 129 const box = ref.current!.getBoundingClientRect()
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 130 setTip({ x: e.clientX - box.left, y: e.clientY - box.top, row })
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 131 }
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 133 const rateX = rateValue !== null ? xOf(rateValue) : 0
134 const rateLabel =
135 rateBits !== null && rateBits > 0
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 137 ? `R = ${rateBits.toFixed(2)}`
138 : `R ⇒ ${(16 / rateBits).toFixed(2)}×`
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 141 return (
142 <div>
143 <div className="chart-header">
144 <div>
145 <div className="segmented" role="group" aria-label="metric">
146 <button className={metric === 'ratio' ? 'active' : ''} onClick={() => setMetric('ratio')}>
147 compression ratio
148 </button>
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 149 <button className={metric === 'bits' ? 'active' : ''} onClick={() => setMetric('bits')}>
150 bits / sample
151 </button>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 152 </div>
153 <span className="metric-hint">
154 {metric === 'bits' ? 'lower is better' : 'vs int16 — higher is better'}
155 </span>
156 </div>
157 <div className="legend">
158 {CODER_NAMES.map((name, i) => (
159 <span key={name}>
160 <span className="swatch" style={{ background: CODER_VARS[i] }} />
161 {name}
162 </span>
163 ))}
165 <span className="swatch hollow" />
166 entropy limit (not achieved)
167 </span>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 168 </div>
169 </div>
170 <div className={`chart-body${props.computing ? ' computing' : ''}`} ref={ref}>
171 {props.computing && <span className="computing-badge">computing…</span>}
172 <svg width={width} height={height}>
173 {axisTicks(xMax).map(v => (
174 <g key={v}>
175 <line x1={xOf(v)} x2={xOf(v)} y1={AXIS_H - 6} y2={height - 4} stroke="var(--grid)" strokeWidth={1} />
176 <text x={xOf(v)} y={AXIS_H - 10} textAnchor="middle" className="axis-tick">
177 {+v.toFixed(1)}
178 </text>
179 </g>
180 ))}
181 <line x1={xOf(0)} x2={xOf(0)} y1={AXIS_H - 6} y2={height - 4} stroke="var(--baseline)" strokeWidth={1} />
182 {GROUPS.map((g, gi) => (
184 key={g}
185 x={0}
186 y={AXIS_H + gi * (GROUP_H + ROWS_PER_GROUP * ROW_H) + 15}
187 className="bar-group-label"
188 >
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 189 {g}
190 </text>
191 ))}
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 192 {rows.map((r, i) => {
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 193 const y = rowY(i)
194 const len = Math.max(1, (value(r) / xMax) * plotW)
195 return (
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 196 <g key={r.key} opacity={hovered === null || hovered === r.key ? 1 : 0.45}>
197 <text
198 x={8}
199 y={y + BAR_H / 2 + 4}
200 className={r.isBound ? 'bar-row-label bound' : 'bar-row-label'}
201 >
202 {r.label}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 203 </text>
205 // Hollow: a limit nobody reached, not a measured size.
206 <path
207 d={barPath(xOf(0), y + 1, len, BAR_H - 2)}
208 fill="var(--muted)"
209 fillOpacity={0.12}
210 stroke="var(--muted)"
211 strokeWidth={1.25}
212 />
213 ) : (
214 <path d={barPath(xOf(0), y, len, BAR_H)} fill={r.color} />
215 )}
216 <text
217 x={xOf(0) + len + 6}
218 y={y + BAR_H / 2 + 4}
219 className={r.isBound ? 'bar-value bound' : 'bar-value'}
220 >
221 {fmt(r)}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 222 </text>
223 <rect
224 x={0}
225 y={y - (ROW_H - BAR_H) / 2}
226 width={width}
227 height={ROW_H}
228 fill="transparent"
229 onPointerMove={e => {
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 230 setHovered(r.key)
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 231 onBarMove(e, r)
232 }}
233 onPointerLeave={() => {
234 setHovered(null)
235 setTip(null)
236 }}
237 />
238 </g>
239 )
240 })}
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 241 {rateValue !== null && (
243 <line
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 244 x1={rateX}
245 x2={rateX}
247 y2={height - 4}
248 stroke="var(--ink-2)"
249 strokeWidth={1.5}
250 strokeDasharray="5 4"
251 />
252 <text
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 253 x={rateX + (rateX > width - 150 ? -6 : 6)}
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 255 textAnchor={rateX > width - 150 ? 'end' : 'start'}
257 fill="var(--ink)"
258 >
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 259 {rateLabel}
261 </g>
262 )}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 263 </svg>
264 {tip && (
265 <div className="viz-tooltip" style={{ left: tip.x + 14, top: tip.y - 8 }}>
266 <div>
267 <span className="tip-value">
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 268 {tip.row.bitsPerSample.toFixed(3)} bits/sample · {tip.row.ratio.toFixed(2)}×
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 269 </span>{' '}
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 270 <span className="tip-label">{tip.row.name}</span>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 271 </div>
272 <div className="tip-label">
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 273 {tip.row.isBound ? 'equivalent to ' : ''}
274 {tip.row.bytes.toLocaleString()} bytes · {tip.row.note}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 275 </div>
276 </div>
277 )}
278 </div>
279 <details className="chart-table">
280 <summary>Table view</summary>
281 <table>
282 <thead>
283 <tr>
284 <th>method</th>
285 <th>bytes</th>
286 <th>bits/sample</th>
287 <th>ratio vs int16</th>
288 </tr>
289 </thead>
290 <tbody>
292 <tr key={r.key}>
293 <td>{r.name}</td>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 294 <td>{r.bytes.toLocaleString()}</td>
295 <td>{r.bitsPerSample.toFixed(3)}</td>
296 <td>{r.ratio.toFixed(3)}</td>
297 </tr>
298 ))}
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 299 {rateBits !== null && rateBits > 0 && (
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 301 <td>entropy rate R (Monte-Carlo)</td>
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 303 <td>{rateBits.toFixed(3)}</td>
304 <td>{(16 / rateBits).toFixed(3)}</td>
306 )}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 307 </tbody>
308 </table>
309 </details>
310 </div>
311 )
312}