// Static figures: hand-drawn SVG, no data and no session behind them. // // A lab shows what a method does once the reader presses run; these show what // it *is*, on first sight and in print. They are deliberately plain: the same // palette as the plots, unicode rather than KaTeX for the few symbols that // appear, and one idea per picture. import type { ReactNode } from 'react' import { ink } from '../plot/palette.ts' export const SANS = 'ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif' export const MONO = 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace' /** Wrapper: a captioned, width-filling SVG with a fixed aspect ratio. */ export function Figure({ width, height, caption, maxWidth, children, }: { width: number height: number caption?: string /** in px; the figure never grows past this even in a wide column */ maxWidth?: number children: ReactNode }) { return (
{children} {caption ?
{caption}
: null}
) } /** Arrowhead markers, one set per figure so the ids never collide. */ export function Arrowheads({ id, colors }: { id: string; colors: string[] }) { return ( {colors.map((c, i) => ( ))} ) } export const arrow = (id: string, i: number) => `url(#${id}-ah-${i})` /** A text label. `anchor` and `baseline` are the usual SVG values. */ export function Label({ x, y, children, fill = ink.secondary, size = 12, anchor = 'middle', baseline = 'middle', italic = false, mono = false, weight, }: { x: number y: number children: ReactNode fill?: string size?: number anchor?: 'start' | 'middle' | 'end' baseline?: 'middle' | 'hanging' | 'auto' | 'central' italic?: boolean mono?: boolean weight?: number }) { return ( {children} ) } /** A grid node. */ export function Node({ x, y, r = 3.2, fill = ink.node, stroke }: { x: number; y: number; r?: number; fill?: string; stroke?: string }) { return }