import { useState, useRef, useEffect } from 'react' import { math2svg, EquationOptions } from '../utils/math2svg' export function EquationRenderer() { // const editor = useEditor() // Not currently used const [isExpanded, setIsExpanded] = useState(false) const [type, setType] = useState<'function' | 'parametric'>('function') const [expression, setExpression] = useState('sin(x)') const [xExpression, setXExpression] = useState('cos(t)') const [yExpression, setYExpression] = useState('sin(t)') // Range const [xMin, setXMin] = useState(-10) const [xMax, setXMax] = useState(10) const [tMin, setTMin] = useState(0) const [tMax, setTMax] = useState(6.28) // Scaling const [scaleX, setScaleX] = useState(20) const [scaleY, setScaleY] = useState(20) const [zoom, setZoom] = useState(1) const [panX] = useState(0) const [panY] = useState(0) // Samples const [samples, setSamples] = useState(200) const [svgPath, setSvgPath] = useState('') const svgRef = useRef(null) // Touch zoom support useEffect(() => { const svg = svgRef.current if (!svg) return let lastDistance = 0 const handleTouchStart = (e: TouchEvent) => { if (e.touches.length === 2) { e.preventDefault() const touch1 = e.touches[0] const touch2 = e.touches[1] lastDistance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ) } } const handleTouchMove = (e: TouchEvent) => { if (e.touches.length === 2) { e.preventDefault() const touch1 = e.touches[0] const touch2 = e.touches[1] const distance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ) if (lastDistance > 0) { const scale = distance / lastDistance setZoom(prev => Math.max(0.1, Math.min(5, prev * scale))) } lastDistance = distance } } svg.addEventListener('touchstart', handleTouchStart, { passive: false }) svg.addEventListener('touchmove', handleTouchMove, { passive: false }) return () => { svg.removeEventListener('touchstart', handleTouchStart) svg.removeEventListener('touchmove', handleTouchMove) } }, []) const generateSVG = () => { const options: EquationOptions = { type, samples, scaleX, scaleY, offsetX: 250, offsetY: 250, flipY: true } if (type === 'function') { options.expression = expression options.xMin = xMin options.xMax = xMax } else { options.xExpression = xExpression options.yExpression = yExpression options.tMin = tMin options.tMax = tMax } const path = math2svg(options) setSvgPath(path) } // Disabled for now - createAssetId is not available in current tldraw version // const insertIntoCanvas = () => { // if (!svgPath) return // alert('Equation inserted! (Feature in progress - will add as shape)') // } if (!isExpanded) { return ( ) } return (
📐 Equations
{type === 'function' ? (
setExpression(e.target.value)} placeholder="e.g., sin(x), x^2" className="tlui-input" style={{ width: '100%' }} />
setXMin(Number(e.target.value))} className="tlui-input" style={{ width: '100%' }} />
setXMax(Number(e.target.value))} className="tlui-input" style={{ width: '100%' }} />
) : (
setXExpression(e.target.value)} placeholder="e.g., cos(t)" style={{ width: '100%', padding: '6px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555' }} />
setYExpression(e.target.value)} placeholder="e.g., sin(t)" style={{ width: '100%', padding: '6px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555' }} />
setTMin(Number(e.target.value))} step="0.1" style={{ width: '100%', padding: '6px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555' }} />
setTMax(Number(e.target.value))} step="0.1" style={{ width: '100%', padding: '6px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555' }} />
)}
setScaleX(Number(e.target.value))} style={{ width: '100%', padding: '4px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555', fontSize: '11px' }} />
setScaleY(Number(e.target.value))} style={{ width: '100%', padding: '4px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555', fontSize: '11px' }} />
setZoom(Number(e.target.value))} step="0.1" min="0.1" max="5" style={{ width: '100%', padding: '4px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555', fontSize: '11px' }} />
setSamples(Number(e.target.value))} min="10" max="1000" style={{ width: '100%', padding: '6px', borderRadius: '4px', background: '#333', color: 'white', border: '1px solid #555' }} />
{svgPath && ( <>
{/* Grid lines for reference */} {/* The equation path */}
Path length: {svgPath.length} chars