index.jsx
5.3 kB · jsx · 109 lines
1import { useRef, useState } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { mount, Page, Row, Pick, Slider, Check, Stats, Stat, Note, Group } from '../../lib/app.jsx';4import { Markup, Sketch } from '../../lib/draw.jsx';5import { useQuery } from '../../lib/query.js';6import { useSeeds, seeded, Picker } from '../../lib/select.jsx';7import { board, bars, axis } from '../../lib/chart.js';89const m = await ready();10const PAD = 12;1112function App() {13 const s = useSeeds();14 const [pick, set] = useQuery({ code: seeded(s, 3, 2, '126'), level: 5 });15 const [height, setHeight] = useState(null);16 const [both, setBoth] = useState(false);17 const [view, setView] = useState('points');18 const shown = useRef(null);1920 const top = m.level_cap(2, 1, view === 'section' ? 64 : 512);21 const level = Math.min(pick.level, top);22 const code = pick.code.trim();2324 let error = null, art = null, drawn = '';25 try {26 const cut = JSON.parse(m.diagonal_profile(code, 2, level, 2));27 const [low, high] = cut.support;28 const at = Math.min(high, Math.max(low, height ?? cut.central[0]));29 const heights = both ? cut.central : [at];30 shown.current = {31 cut, low, high, at, heights,32 name: m.name_of(code, 3, 2),33 here: heights.map((h) => m.diagonal_count(code, 2, level, 2, h)).join(' and '),34 digits: heights.map((h) => m.diagonal_digits(code, 2, level, 2, h)).join(' and '),35 };36 const svg = view === 'section'37 ? m.hex_svg(code, 2, level, 2, 'cut', Math.max(1, Math.round(256 / 2 ** level)))38 : m.diagonal_svg(code, 2, level, 2, heights, Math.max(1, Math.round(512 / 2 ** level)));39 if (svg.length > 4000000) throw new Error('that drawing is larger than this page serves; lower the level.');40 art = svg;41 drawn = view === 'section' ? '-' : m.diagonal_total(code, 2, level, 2, heights);42 } catch (fault) {43 error = fault;44 }4546 const chart = (canvas) => {47 const v = shown.current;48 if (!v) return;49 const b = board(canvas, 180, { pad: PAD, top: 12, bottom: 20 });50 const counts = v.cut.counts.map(Number);51 bars(b, counts, { color: (i) => (v.heights.includes(v.low + i) ? ink.yellow : ink.blue) });52 axis(b, [[0, v.low], [1, v.low + counts.length - 1]]);53 };5455 const onSeek = (frac) => {56 const v = shown.current;57 if (both || !v) return;58 setHeight(Math.min(v.high, Math.max(v.low, Math.round(v.low + frac * (v.high - v.low)))));59 };6061 const onView = (value) => {62 const cap = m.level_cap(2, 1, value === 'section' ? 64 : 512);63 setView(value);64 if (pick.level > cap) {65 set({ level: cap });66 setHeight(null);67 }68 };6970 const v = shown.current;7172 const controls = (73 <>74 <Group name="Design">75 <Picker dimension={3} code={pick.code} seeds={s} onChange={(patch) => { set(patch); setHeight(null); }} />76 <Slider label="level" value={level} min={1} max={top} onChange={(value) => { set({ level: value }); setHeight(null); }} />77 </Group>78 <Group name="Plane">79 <label>height <input type="range" min={v ? v.low : 0} max={v ? v.high : 1} value={v ? v.at : 0} disabled={both} onChange={(e) => setHeight(+e.target.value)} /><span className="num">{v?.heights.join(' and ')}</span></label>80 <Check label="both central heights" checked={both} onChange={setBoth} />81 <Pick label="view" value={view} options={['points', 'section']} onChange={onView} />82 </Group>83 </>84 );8586 return (87 <Page crumb="cuts" title="Every diagonal cut is the same size" controls={controls}88 sub={<>A plane <code>x + y + z = s</code> slides through the solid of <code>bang dim 3, code 126</code> at the level and meets exactly 3^level cells at every height it meets at all. The binary digits of the height say which corners each scale may use, so every cut is a Sierpinski gasket, and the two central heights together fall into six of them tiling a hexagon. Drag the bar chart to move the plane.</>}89 foot={<>The profile is the coefficient list of the digit polynomial, so a height is counted without building a single cell; the points are enumerated on the plane itself and projected down the <code>(1,1,1)</code> axis in Rust, one circle per cell, coloured by height and by top-scale corner. The section view is the crate's own triangular mesh through the same solid. Why every one of these cuts is a gasket, and which one the height's binary digits pick, is in <a href="/research/cuts/">the cuts note</a>.</>}>90 <Sketch draw={chart} deps={[v]} onSeek={onSeek} pad={PAD} className="bars" role="img" aria-label="Cells per height, drag to move the plane" />91 <Stats>92 <Stat label="name">{v?.name}</Stat>93 <Stat label="side">{v?.cut.side}</Stat>94 <Stat label="support">{v && `[${v.low}, ${v.high}]`}</Stat>95 <Stat label="non-empty">{v && `${v.cut.nonempty} of ${v.cut.heights}`}</Stat>96 <Stat label="this slice">{v?.here}</Stat>97 <Stat label="min">{v?.cut.min}</Stat>98 <Stat label="max">{v?.cut.max}</Stat>99 <Stat label="constant">{v && (v.cut.constant ? 'yes' : 'no')}</Stat>100 <Stat label="offset in binary">{v?.digits}</Stat>101 <Stat label="points drawn">{drawn}</Stat>102 </Stats>103 <Markup svg={art ?? ''} role="img" aria-label="The diagonal cut" />104 <Note error={error} />105 </Page>106 );107}108109mount(<App />);