/ concept-collection / barycentric-rational
Sign in
concept-collection / barycentric-rational
barycentric-rational / src / panels / Controls.tsx
150 lines · 4.3 KBBlameHistoryRaw
1import type { FuncName, NodeKind } from '../engine/types.ts'
3export interface Settings {
4 f: FuncName
5 fexpr: string
6 a: number
7 b: number
8 n: number
9 d: number
10 nodes: NodeKind
11 seed: number
14const FUNCS: { id: FuncName; label: string; hint: string }[] = [
15 { id: 'runge', label: '1 / (1 + x²)', hint: "Runge's example, the paper's Figure 1" },
16 { id: 'sine', label: 'sin x', hint: 'Figure 2, smooth everywhere' },
17 { id: 'abs', label: '|x|', hint: 'Figure 3, a corner at 0' },
18 { id: 'custom', label: 'custom', hint: 'any MATLAB expression in x' },
21const NODES: { id: NodeKind; label: string; hint: string }[] = [
22 { id: 'uniform', label: 'uniform', hint: 'equally spaced, as in every figure of the paper' },
23 { id: 'chebyshev', label: 'Chebyshev', hint: 'clustered at both ends' },
24 { id: 'random', label: 'random', hint: 'sorted uniform draws, endpoints kept' },
25 { id: 'paired', label: 'paired', hint: 'every second node pulled close to its neighbour' },
26 { id: 'graded', label: 'graded', hint: 'quadratically clustered at the left end' },
29interface Props {
30 value: Settings
31 onChange: (patch: Partial<Settings>) => void
32 /** the convergence tab sets n itself */
33 showN: boolean
34 busy: boolean
37export default function Controls({ value: v, onChange, showN, busy }: Props) {
38 const dMax = Math.min(v.n, 12)
39 return (
40 <div className="controls">
41 <div className="field">
42 <span className="field-label">function f</span>
43 <div className="chips">
44 {FUNCS.map((fn) => (
45 <button
46 key={fn.id}
47 title={fn.hint}
48 className={`chip ${v.f === fn.id ? 'on' : ''}`}
49 onClick={() => onChange({ f: fn.id })}
50 >
51 {fn.label}
52 </button>
53 ))}
54 </div>
55 </div>
57 {v.f === 'custom' && (
58 <div className="field grow">
59 <span className="field-label">f(x) =</span>
60 <input
61 className="expr"
62 value={v.fexpr}
63 spellCheck={false}
64 placeholder="exp(-x.^2) .* cos(3*x)"
65 onChange={(e) => onChange({ fexpr: e.target.value })}
66 />
67 </div>
68 )}
70 <div className="field">
71 <span className="field-label">interval</span>
72 <div className="interval">
73 <input
74 type="number"
75 value={v.a}
76 step={1}
77 onChange={(e) => {
78 const a = Number(e.target.value)
79 if (isFinite(a) && a < v.b) onChange({ a })
80 }}
81 />
82 <span>to</span>
83 <input
84 type="number"
85 value={v.b}
86 step={1}
87 onChange={(e) => {
88 const b = Number(e.target.value)
89 if (isFinite(b) && b > v.a) onChange({ b })
90 }}
91 />
92 </div>
93 </div>
95 {showN && (
96 <div className="field grow">
97 <span className="field-label">
98 nodes n = <b>{v.n}</b>
99 </span>
100 <input
101 type="range"
102 min={2}
103 max={80}
104 value={v.n}
105 disabled={busy}
106 onChange={(e) => {
107 const n = Number(e.target.value)
108 onChange({ n, d: Math.min(v.d, Math.min(n, 12)) })
109 }}
110 />
111 </div>
112 )}
114 <div className="field grow">
115 <span className="field-label">
116 blend degree d = <b>{v.d}</b>
117 </span>
118 <input
119 type="range"
120 min={0}
121 max={dMax}
122 value={Math.min(v.d, dMax)}
123 disabled={busy}
124 onChange={(e) => onChange({ d: Number(e.target.value) })}
125 />
126 </div>
128 <div className="field">
129 <span className="field-label">node distribution</span>
130 <div className="chips">
131 {NODES.map((nd) => (
132 <button
133 key={nd.id}
134 title={nd.hint}
135 className={`chip ${v.nodes === nd.id ? 'on' : ''}`}
136 onClick={() => onChange({ nodes: nd.id })}
137 >
138 {nd.label}
139 </button>
140 ))}
141 {v.nodes === 'random' && (
142 <button className="chip" onClick={() => onChange({ seed: v.seed + 1 })} title="new draw">
143 reroll
144 </button>
145 )}
146 </div>
147 </div>
148 </div>
149 )
moveopenescclose