/ proof-of-concept / phase-perception-2
Sign in
proof-of-concept / phase-perception-2
phase-perception-2 / src / components / ImagePlot.tsx
101 lines · 3.4 KBCodeBlameHistory
2import { useCanvas } from '../hooks/useCanvas'
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 3import { AXIS, FONT, FRAME, formatTick, niceTicks } from '../plot'
4import type { ImageData2D } from '../types'
6type Props = {
8 /** maps a data value to rgb */
9 color: (v: number) => [number, number, number]
7886a79Waveforms: wheel zoom, drag pan, double-click reset, shared view marked on the spectrogramsJeremy Magland 12 /** time range to outline, e.g. the waveform view */
13 highlight?: [number, number]
17const M = { l: 48, r: 12, t: 8, b: 26 }
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 19/** Draws a time-frequency image (spectrogram, phase field) with axes. */
20export function ImagePlot({ image, color, label, labelColor, highlight, className }: Props) {
21 // Rasterize once per image, then scale to the canvas on every draw.
22 const raster = useMemo(() => {
23 const { rows, cols, data } = image
25 off.width = cols
26 off.height = rows
27 const ictx = off.getContext('2d')
28 if (!ictx) return null
29 const img = ictx.createImageData(cols, rows)
30 const d = img.data
31 for (let r = 0; r < rows; r++) {
32 const yImg = rows - 1 - r // row 0 (0 Hz) at the bottom
33 for (let c = 0; c < cols; c++) {
36 d[o] = R
37 d[o + 1] = G
38 d[o + 2] = B
39 d[o + 3] = 255
40 }
41 }
42 ictx.putImageData(img, 0, 0)
43 return off
46 const draw = useCallback(
47 (ctx: CanvasRenderingContext2D, w: number, h: number) => {
48 const pw = w - M.l - M.r
49 const ph = h - M.t - M.b
50 ctx.font = FONT
55 ctx.strokeStyle = FRAME
56 ctx.lineWidth = 1
57 ctx.strokeRect(M.l + 0.5, M.t + 0.5, pw, ph)
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 59 const x0 = M.l + (Math.max(0, highlight[0]) / image.durationSec) * pw
60 const x1 = M.l + (Math.min(image.durationSec, highlight[1]) / image.durationSec) * pw
7886a79Waveforms: wheel zoom, drag pan, double-click reset, shared view marked on the spectrogramsJeremy Magland 61 ctx.strokeStyle = 'rgba(255, 255, 255, 0.85)'
62 ctx.setLineDash([4, 3])
63 ctx.strokeRect(Math.round(x0) + 0.5, M.t + 0.5, Math.max(2, Math.round(x1 - x0)), ph)
64 ctx.setLineDash([])
65 }
67 ctx.fillStyle = AXIS
68 ctx.textAlign = 'right'
69 ctx.textBaseline = 'middle'
71 if (v > image.fmax) continue
72 const y = M.t + ph - (v / image.fmax) * ph
74 }
75 ctx.textAlign = 'center'
76 ctx.textBaseline = 'top'
78 if (v > image.durationSec) continue
79 const x = M.l + (v / image.durationSec) * pw
81 }
82 ctx.textBaseline = 'bottom'
83 ctx.fillText('time (s)', M.l + pw / 2, h - 1)
84 ctx.save()
85 ctx.translate(11, M.t + ph / 2)
86 ctx.rotate(-Math.PI / 2)
87 ctx.textBaseline = 'middle'
88 ctx.fillText('Hz', 0, 0)
89 ctx.restore()
93 ctx.textAlign = 'left'
94 ctx.textBaseline = 'top'
95 ctx.fillText(label, M.l + 6, M.t + 5)
96 },
99 const ref = useCanvas(draw)
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 100 return <canvas ref={ref} className={`plot ${className ?? 'image-plot'}`} />
↑↓ move↵openescclose