import React, { useRef, useState } from "react"; import { scaleToSvg, scaleFromSvg, round, calculateDistanceSquared, } from "../../utils/math"; import { type CircleState, type Point } from "../../types/lesson"; interface CoordinatePlaneProps { circle: CircleState; point?: Point | null; onPointClick?: (p: Point) => void; interactive?: boolean; showDistance?: boolean; mode?: "view" | "place_point"; } const CoordinatePlane: React.FC = ({ circle, point, onPointClick, showDistance = false, mode = "view", }) => { const svgRef = useRef(null); const [hoverPoint, setHoverPoint] = useState(null); // Viewport settings const width = 400; const height = 400; const range = 10; // -10 to 10 const tickSpacing = 1; // Scales const toX = (val: number) => scaleToSvg(val, -range, range, 0, width); const toY = (val: number) => scaleToSvg(val, range, -range, 0, height); // Inverted Y for SVG const fromX = (px: number) => scaleFromSvg(px, -range, range, 0, width); const fromY = (px: number) => scaleFromSvg(px, range, -range, 0, height); const cx = toX(circle.h); const cy = toY(circle.k); // Radius in pixels (assuming uniform aspect ratio) const rPx = toX(circle.r) - toX(0); const handleMouseMove = (e: React.MouseEvent) => { if (mode !== "place_point" || !svgRef.current) return; const rect = svgRef.current.getBoundingClientRect(); const rawX = e.clientX - rect.left; const rawY = e.clientY - rect.top; // Snap to nearest 0.5 for cleaner UX const graphX = Math.round(fromX(rawX) * 2) / 2; const graphY = Math.round(fromY(rawY) * 2) / 2; setHoverPoint({ x: graphX, y: graphY }); }; const handleClick = () => { if (mode === "place_point" && hoverPoint && onPointClick) { onPointClick(hoverPoint); } }; // Generate grid lines const ticks = []; for (let i = -range; i <= range; i += tickSpacing) { if (i === 0) continue; // Skip axes (drawn separately) ticks.push(i); } const dSquared = point ? calculateDistanceSquared(point.x, point.y, circle.h, circle.k) : 0; const isInside = dSquared < circle.r * circle.r; const isOn = Math.abs(dSquared - circle.r * circle.r) < 0.01; const pointFill = isOn ? "#ca8a04" : isInside ? "#16a34a" : "#dc2626"; return (
setHoverPoint(null)} onClick={handleClick} className={`${mode === "place_point" ? "cursor-crosshair" : "cursor-default"}`} > {/* Grid Background */} {ticks.map((t) => ( ))} {/* Axes */} {/* Circle */} {/* Center Point */} Center ({circle.h}, {circle.k}) {/* Radius Line (only if distance line is not active to avoid clutter) */} {!point && ( )} {!point && ( r = {circle.r} )} {/* Placed Point */} {point && ( <> ({point.x}, {point.y}) )} {/* Hover Ghost Point */} {mode === "place_point" && hoverPoint && !point && ( )}
1 unit = {width / (range * 2)}px
{/* Info Panel below graph */} {point && showDistance && (
Distance Check: {isOn ? "On Circle" : isInside ? "Inside" : "Outside"}

d² = (x - h)² + (y - k)²

d² = ({point.x} - {circle.h})² + ({point.y} - {circle.k})²

d² ={" "} {round( calculateDistanceSquared(point.x, point.y, circle.h, circle.k), )}{" "} vs r² ={" "} {circle.r * circle.r}

)}
); }; export default CoordinatePlane;