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 />);