import { useState } from 'react' import Plot from '../plot/Plot.tsx' import Legend, { type LegendItem } from '../plot/Legend.tsx' import More from './More.tsx' import SeriesToggle from './SeriesToggle.tsx' import { ink, series } from '../plot/palette.ts' import { extent, linePath, nearestIndex, padDomain, type Frame } from '../plot/scales.ts' import type { ExploreOut, Num } from '../engine/types.ts' interface Props { out: ExploreOut showPoly: boolean showSpline: boolean onToggle: (which: 'poly' | 'spline', on: boolean) => void } function fmtErr(v: Num): string { if (v == null || !isFinite(v)) return 'off scale' if (v === 0) return '0' return v.toExponential(1) } /** Largest |value| of a series, ignoring the parts that ran off to infinity. */ function maxAbs(v: readonly Num[] | undefined, ref: readonly number[]): Num { if (!v) return null let m = 0 let any = false for (let i = 0; i < v.length; i++) { const d = v[i] if (d == null || !isFinite(d)) continue any = true m = Math.max(m, Math.abs(d - ref[i])) } return any ? m : null } export default function InterpolantPanel({ out, showPoly, showSpline, onToggle }: Props) { const [hoverX, setHoverX] = useState(null) // The polynomial interpolant at equispaced nodes is the whole point of the // paper's opening paragraph, and at n = 40 it is off scale by a factor of // 10^8. Scale the axis to f and to r, and let the rest leave the frame. const yd = padDomain(extent(out.ft, out.r, showSpline ? out.rspline : undefined), 0.1) const errOf = (v: readonly Num[] | undefined): Num[] | undefined => v ? v.map((d, i) => (d == null ? null : d - out.ft[i])) : undefined const errPoly = showPoly ? errOf(out.rpoly) : undefined const errSpline = showSpline ? errOf(out.rspline) : undefined const eAll = extent(out.err, errPoly, errSpline) const eMax = Math.max(Math.abs(eAll[0]), Math.abs(eAll[1]), 1e-16) const ed: [number, number] = [-eMax * 1.1, eMax * 1.1] const hi = hoverX == null ? -1 : nearestIndex(out.t, hoverX) const at = (v: readonly Num[] | undefined) => (v && hi >= 0 ? v[hi] : null) const legend: LegendItem[] = [ { label: 'f(x)', color: ink.reference, dash: '5 4', value: undefined }, { label: 'rational r(x)', color: series.r, value: fmtErr(out.maxerr) }, ] if (showPoly) { legend.push({ label: `polynomial (degree ${out.n})`, color: series.poly, value: fmtErr(maxAbs(out.rpoly, out.ft)), }) } if (showSpline) { legend.push({ label: 'cubic spline', color: series.spline, value: fmtErr(maxAbs(out.rspline, out.ft)) }) } legend.push({ label: `${out.n + 1} nodes`, color: ink.node, shape: 'dot' }) const nodes = (f: Frame) => out.x.map((xv, i) => ( )) return (

The rational interpolant r passes exactly through all {out.n + 1} data points and stays close to the function f everywhere in between. The controls above change the data; the buttons below add two standard alternatives fitted to the same points.

This is r of equation (1) with the weights of equation (18), using blend degree d = {out.d}. The degree-{out.n} polynomial through the same points is the paper's opening example: on equally spaced nodes it diverges as n grows, which is Runge's phenomenon. The clamped C2 cubic spline is the standard the paper measures itself against in Tables 3 and 4.

compare with onToggle('poly', on)}> polynomial (degree {out.n}) onToggle('spline', on)}> cubic spline
{(f) => ( <> {showSpline && out.rspline && ( )} {showPoly && out.rpoly && ( )} {nodes(f)} )} {/* always rendered, so the error plot below never jumps when hovering */}
{hi >= 0 ? ( <> x = {out.t[hi].toFixed(3)} f = {out.ft[hi].toFixed(6)} r = {at(out.r)?.toFixed(6) ?? '-'} {showPoly && ( poly = {fmtSigned(at(out.rpoly))} )} {showSpline && ( spline = {at(out.rspline)?.toFixed(6) ?? '-'} )} ) : ( hover a plot to read values )}

Error

The difference r − f. It is zero at every node by construction; the largest bump between nodes is the number the paper's tables report.

} > {(f) => ( <> {errSpline && } {errPoly && } {out.x.map((xv, i) => ( ))} )}
) } function fmtSigned(v: Num): string { if (v == null || !isFinite(v)) return 'off scale' if (Math.abs(v) >= 1e5) return v.toExponential(2) return v.toFixed(6) }