Adds a full-featured mathematical calculator and interactive function grapher at /calculate. Powered by Math.js v15 with a HiDPI Canvas renderer for the graph. - Evaluates arbitrary math expressions (trig, log, complex, matrices, factorials, combinatorics, and more) with named variable scope - Persists history (50 entries) and variables via localStorage - 32 quick-insert buttons for constants and functions - Interactive graph: pan (drag), zoom (scroll), crosshair tooltip showing cursor coords and f₁(x)…f₈(x) values simultaneously - Up to 8 color-coded functions with inline color pickers and visibility toggles - Discontinuity detection for functions like tan(x) - Adaptive grid labels that rescale with zoom - Responsive layout: 2/5–3/5 split on desktop, tabbed on mobile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
2.8 KiB
TypeScript
108 lines
2.8 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
|
|
export const FUNCTION_COLORS = [
|
|
'#f472b6',
|
|
'#60a5fa',
|
|
'#4ade80',
|
|
'#fb923c',
|
|
'#a78bfa',
|
|
'#22d3ee',
|
|
'#fbbf24',
|
|
'#f87171',
|
|
];
|
|
|
|
export interface HistoryEntry {
|
|
id: string;
|
|
expression: string;
|
|
result: string;
|
|
error: boolean;
|
|
}
|
|
|
|
export interface GraphFunction {
|
|
id: string;
|
|
expression: string;
|
|
color: string;
|
|
visible: boolean;
|
|
}
|
|
|
|
interface CalculateStore {
|
|
expression: string;
|
|
history: HistoryEntry[];
|
|
variables: Record<string, string>;
|
|
graphFunctions: GraphFunction[];
|
|
setExpression: (expr: string) => void;
|
|
addToHistory: (entry: Omit<HistoryEntry, 'id'>) => void;
|
|
clearHistory: () => void;
|
|
setVariable: (name: string, value: string) => void;
|
|
removeVariable: (name: string) => void;
|
|
addGraphFunction: () => void;
|
|
updateGraphFunction: (
|
|
id: string,
|
|
updates: Partial<Pick<GraphFunction, 'expression' | 'color' | 'visible'>>
|
|
) => void;
|
|
removeGraphFunction: (id: string) => void;
|
|
}
|
|
|
|
const uid = () => `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
|
|
export const useCalculateStore = create<CalculateStore>()(
|
|
persist(
|
|
(set) => ({
|
|
expression: '',
|
|
history: [],
|
|
variables: {},
|
|
graphFunctions: [
|
|
{ id: 'init-1', expression: 'sin(x)', color: FUNCTION_COLORS[0], visible: true },
|
|
{ id: 'init-2', expression: 'cos(x)', color: FUNCTION_COLORS[1], visible: true },
|
|
],
|
|
|
|
setExpression: (expression) => set({ expression }),
|
|
|
|
addToHistory: (entry) =>
|
|
set((state) => ({
|
|
history: [{ ...entry, id: uid() }, ...state.history].slice(0, 50),
|
|
})),
|
|
|
|
clearHistory: () => set({ history: [] }),
|
|
|
|
setVariable: (name, value) =>
|
|
set((state) => ({ variables: { ...state.variables, [name]: value } })),
|
|
|
|
removeVariable: (name) =>
|
|
set((state) => {
|
|
const v = { ...state.variables };
|
|
delete v[name];
|
|
return { variables: v };
|
|
}),
|
|
|
|
addGraphFunction: () =>
|
|
set((state) => {
|
|
const used = new Set(state.graphFunctions.map((f) => f.color));
|
|
const color =
|
|
FUNCTION_COLORS.find((c) => !used.has(c)) ??
|
|
FUNCTION_COLORS[state.graphFunctions.length % FUNCTION_COLORS.length];
|
|
return {
|
|
graphFunctions: [
|
|
...state.graphFunctions,
|
|
{ id: uid(), expression: '', color, visible: true },
|
|
],
|
|
};
|
|
}),
|
|
|
|
updateGraphFunction: (id, updates) =>
|
|
set((state) => ({
|
|
graphFunctions: state.graphFunctions.map((f) =>
|
|
f.id === id ? { ...f, ...updates } : f
|
|
),
|
|
})),
|
|
|
|
removeGraphFunction: (id) =>
|
|
set((state) => ({
|
|
graphFunctions: state.graphFunctions.filter((f) => f.id !== id),
|
|
})),
|
|
}),
|
|
{ name: 'kit-calculate-v1' }
|
|
)
|
|
);
|