widget.jsx
3.4 kB · jsx · 74 lines
1import { useEffect, useMemo, useState } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { Row, Slider, Pick } from '../../lib/app.jsx';4import { Sketch } from '../../lib/draw.jsx';5import { board, line, axis, tag } from '../../lib/chart.js';6import { embed } from '../../lib/widget.jsx';78const m = await ready();910export const LOW = 8;11export const HIGH = 20;12export const PER_OCTAVE = 16;13export const ZEROS = 138;14export const CURVE = 900;15const SHORT = 16;16const FEW = 40;1718export const meterChart = ({ heights, dots, curve, wave, j, k, sharp, height }) => (canvas) => {19 const b = board(canvas, height, { top: 26, bottom: 22 });20 if (!dots.length) return;21 const wide = j - LOW;22 let span = 1e-9;23 for (let i = 0; i < heights.length; i++) if (heights[i] <= j) span = Math.max(span, Math.abs(dots[i]));24 for (const v of wave) span = Math.max(span, Math.abs(v));25 span *= 1.1;26 b.ctx.strokeStyle = ink.line;27 b.ctx.lineWidth = 1;28 b.ctx.beginPath();29 b.ctx.moveTo(b.x(0), b.y(0.5));30 b.ctx.lineTo(b.x(1), b.y(0.5));31 b.ctx.stroke();32 if (k > 0) {33 const points = Array.from(wave, (v, i) => [(curve[i] - LOW) / wide, 0.5 + 0.5 * v / span]);34 line(b, points, ink.orange, { width: 1.5 });35 }36 b.ctx.fillStyle = ink.blue;37 for (let i = 0; i < heights.length; i++) {38 if (heights[i] > j) break;39 b.ctx.beginPath();40 b.ctx.arc(b.x((heights[i] - LOW) / wide), b.y(0.5 + 0.5 * dots[i] / span), 2.6, 0, Math.PI * 2);41 b.ctx.fill();42 }43 axis(b, [[0, `y = 2^-${LOW}`], [1, `y = 2^-${j}`]]);44 tag(b, sharp ? 'E(y) / y, the sharp window' : 'E(y) / y^(3/2), the smooth window', ink.blue);45 const whose = k === 1 ? 'the first zero' : `the first ${k} zeros`;46 tag(b, k ? (sharp ? `the wave of ${whose}, times sqrt(y)` : `the wave of ${whose}`) : 'no zeros', ink.orange, 'right');47};4849export function meter() {50 const [k, setK] = useState(1);51 const [cut, setCut] = useState('smooth');52 const [reading, setReading] = useState(null);53 useEffect(() => {54 setReading(new m.Novelty(SHORT, PER_OCTAVE, FEW));55 }, []);56 const sharp = cut === 'sharp';57 const heights = useMemo(() => (reading ? reading.heights() : new Float64Array()), [reading]);58 const dots = useMemo(() => (reading ? reading.dots(sharp) : new Float64Array()), [reading, sharp]);59 const curve = useMemo(() => Float64Array.from({ length: CURVE + 1 }, (_, i) => LOW + (SHORT - LOW) * i / CURVE), []);60 const wave = useMemo(() => (reading ? reading.wave(k, sharp, curve) : new Float64Array()), [reading, k, sharp, curve]);61 const miss = useMemo(() => (reading ? reading.miss(k) : 1), [reading, k]);62 return (63 <>64 <Sketch draw={meterChart({ heights, dots, curve, wave, j: SHORT, k, sharp, height: 220 })} deps={[reading, dots, wave, k, sharp]} className="bars" role="img" aria-label="The novelty error as dots with the wave of the first zeros as a curve" />65 <Row>66 <Slider label="zeros" value={k} min={0} max={FEW} onChange={setK} />67 <Pick label="window" value={cut} options={[['smooth', 'smooth bump'], ['sharp', 'sharp cutoff']]} onChange={setCut} />68 </Row>69 <p className="sub">{reading ? (sharp ? `the sharp window's error over y, ${heights.length} readings the waves of ${k} zeros cannot reach` : `${k} of ${FEW} zeros miss the ${heights.length} smoothed readings by ${miss.toExponential(1)} of their peak`) : 'sieving the totients'}</p>70 </>71 );72}7374embed('novelty', { meter });