|
@ -63,9 +63,9 @@ export interface MeasurementCanvasProps { |
|
|
initialMeasurementDataLeft?: Point[]; |
|
|
initialMeasurementDataLeft?: Point[]; |
|
|
initialMeasurementDataRight?: Point[]; |
|
|
initialMeasurementDataRight?: Point[]; |
|
|
initialAnalysisData?: AnalysisData[]; |
|
|
initialAnalysisData?: AnalysisData[]; |
|
|
// 新增属性:控制是否显示标准线(benchmark shapes)
|
|
|
|
|
|
|
|
|
// 控制是否显示标准线(benchmark shapes)
|
|
|
showBenchmark?: boolean; |
|
|
showBenchmark?: boolean; |
|
|
// 新增属性:控制是否显示分析线
|
|
|
|
|
|
|
|
|
// 控制是否显示分析线
|
|
|
showAnalysis?: boolean; |
|
|
showAnalysis?: boolean; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -80,6 +80,13 @@ export interface MeasurementCanvasRef { |
|
|
redraw: () => void; |
|
|
redraw: () => void; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
interface PinchData { |
|
|
|
|
|
initialDistance: number; |
|
|
|
|
|
initialScale: number; |
|
|
|
|
|
initialOffset: { x: number; y: number }; |
|
|
|
|
|
initialCenter: Point; // 固定的缩放中心
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProps>( |
|
|
const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProps>( |
|
|
(props, ref) => { |
|
|
(props, ref) => { |
|
|
const { |
|
|
const { |
|
@ -100,19 +107,18 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
initialMeasurementDataLeft = [], |
|
|
initialMeasurementDataLeft = [], |
|
|
initialMeasurementDataRight = [], |
|
|
initialMeasurementDataRight = [], |
|
|
initialAnalysisData = [], |
|
|
initialAnalysisData = [], |
|
|
showBenchmark = true, // 默认显示标准线
|
|
|
|
|
|
showAnalysis = true, // 默认显示分析线
|
|
|
|
|
|
|
|
|
showBenchmark = true, |
|
|
|
|
|
showAnalysis = true, |
|
|
} = props; |
|
|
} = props; |
|
|
|
|
|
|
|
|
// Stage 物理中心(像素)
|
|
|
// Stage 物理中心(像素)
|
|
|
const canvasCenter = { x: width / 2, y: height / 2 }; |
|
|
const canvasCenter = { x: width / 2, y: height / 2 }; |
|
|
|
|
|
|
|
|
// 当 logicalExtent 范围较小时,自动计算一个 scale 使其铺满整个 Stage
|
|
|
|
|
|
|
|
|
// 计算初始 scale 与 offset
|
|
|
const logicalWidth = logicalExtent.maxX - logicalExtent.minX; |
|
|
const logicalWidth = logicalExtent.maxX - logicalExtent.minX; |
|
|
const logicalHeight = logicalExtent.maxY - logicalExtent.minY; |
|
|
const logicalHeight = logicalExtent.maxY - logicalExtent.minY; |
|
|
const computedScale = Math.min(width / logicalWidth, height / logicalHeight); |
|
|
const computedScale = Math.min(width / logicalWidth, height / logicalHeight); |
|
|
const initialScale = computedScale > pixelPerMm ? computedScale : pixelPerMm; |
|
|
const initialScale = computedScale > pixelPerMm ? computedScale : pixelPerMm; |
|
|
|
|
|
|
|
|
const logicalCenter = { |
|
|
const logicalCenter = { |
|
|
x: (logicalExtent.minX + logicalExtent.maxX) / 2, |
|
|
x: (logicalExtent.minX + logicalExtent.maxX) / 2, |
|
|
y: (logicalExtent.minY + logicalExtent.maxY) / 2, |
|
|
y: (logicalExtent.minY + logicalExtent.maxY) / 2, |
|
@ -127,28 +133,17 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
|
|
|
|
|
|
const [offset, setOffset] = useState<{ x: number; y: number }>(initialOffset); |
|
|
const [offset, setOffset] = useState<{ x: number; y: number }>(initialOffset); |
|
|
const [scale, setScale] = useState<number>(initialScale); |
|
|
const [scale, setScale] = useState<number>(initialScale); |
|
|
const [benchmarkData, setBenchmarkData] = |
|
|
|
|
|
useState<BenchmarkShape[]>(initialBenchmarkData); |
|
|
|
|
|
const [analysisData, setAnalysisData] = |
|
|
|
|
|
useState<AnalysisData[]>(initialAnalysisData); |
|
|
|
|
|
|
|
|
const [benchmarkData, setBenchmarkData] = useState<BenchmarkShape[]>(initialBenchmarkData); |
|
|
|
|
|
const [analysisData, setAnalysisData] = useState<AnalysisData[]>(initialAnalysisData); |
|
|
|
|
|
|
|
|
// 左右测量数据使用定时器更新
|
|
|
|
|
|
|
|
|
// 定时更新测量数据(左右两侧)
|
|
|
const leftPointsRef = useRef<Point[]>([...initialMeasurementDataLeft]); |
|
|
const leftPointsRef = useRef<Point[]>([...initialMeasurementDataLeft]); |
|
|
const rightPointsRef = useRef<Point[]>([...initialMeasurementDataRight]); |
|
|
const rightPointsRef = useRef<Point[]>([...initialMeasurementDataRight]); |
|
|
|
|
|
|
|
|
const [measurementDataLeft, setMeasurementDataLeftState] = useState<Point[]>( |
|
|
|
|
|
initialMeasurementDataLeft |
|
|
|
|
|
); |
|
|
|
|
|
const [measurementDataRight, setMeasurementDataRightState] = useState<Point[]>( |
|
|
|
|
|
initialMeasurementDataRight |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 新增直接绘制的测量数据状态,不使用定时器
|
|
|
|
|
|
|
|
|
const [measurementDataLeft, setMeasurementDataLeftState] = useState<Point[]>(initialMeasurementDataLeft); |
|
|
|
|
|
const [measurementDataRight, setMeasurementDataRightState] = useState<Point[]>(initialMeasurementDataRight); |
|
|
const [measurementData, setMeasurementDataState] = useState<Point[]>([]); |
|
|
const [measurementData, setMeasurementDataState] = useState<Point[]>([]); |
|
|
|
|
|
|
|
|
const refreshInterval = 50; |
|
|
const refreshInterval = 50; |
|
|
const refreshTimer = useRef<number | null>(null); |
|
|
const refreshTimer = useRef<number | null>(null); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
if (!refreshTimer.current) { |
|
|
if (!refreshTimer.current) { |
|
|
refreshTimer.current = window.setInterval(() => { |
|
|
refreshTimer.current = window.setInterval(() => { |
|
@ -199,80 +194,122 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
|
|
|
|
|
|
const stageRef = useRef<any>(null); |
|
|
const stageRef = useRef<any>(null); |
|
|
|
|
|
|
|
|
const transform = (pt: Point) => ({ |
|
|
|
|
|
x: canvasCenter.x + offset.x + (pt.x - origin.x) * scale, |
|
|
|
|
|
y: canvasCenter.y + offset.y + (pt.y - origin.y) * scale, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const clampOffset = (newOffset: { x: number; y: number }, currentScale: number) => { |
|
|
|
|
|
const left = canvasCenter.x + newOffset.x + (logicalExtent.minX - origin.x) * currentScale; |
|
|
|
|
|
const right = canvasCenter.x + newOffset.x + (logicalExtent.maxX - origin.x) * currentScale; |
|
|
|
|
|
const top = canvasCenter.y + newOffset.y + (logicalExtent.minY - origin.y) * currentScale; |
|
|
|
|
|
const bottom = canvasCenter.y + newOffset.y + (logicalExtent.maxY - origin.y) * currentScale; |
|
|
|
|
|
let clampedX = newOffset.x; |
|
|
|
|
|
let clampedY = newOffset.y; |
|
|
|
|
|
if (left > 0) clampedX -= left; |
|
|
|
|
|
if (right < width) clampedX += width - right; |
|
|
|
|
|
if (top > 0) clampedY -= top; |
|
|
|
|
|
if (bottom < height) clampedY += height - bottom; |
|
|
|
|
|
return { x: clampedX, y: clampedY }; |
|
|
|
|
|
|
|
|
// 记录当前活跃 pointer(pointerId -> Point)
|
|
|
|
|
|
const pointersRef = useRef<Map<number, Point>>(new Map()); |
|
|
|
|
|
// 单指拖拽的上一次位置
|
|
|
|
|
|
const lastDragPosRef = useRef<Point | null>(null); |
|
|
|
|
|
// 针对鼠标,记录是否按下
|
|
|
|
|
|
const isDraggingRef = useRef<boolean>(false); |
|
|
|
|
|
// 记录双指缩放的初始数据(只在两指刚开始时记录一次)
|
|
|
|
|
|
const pinchDataRef = useRef<PinchData | null>(null); |
|
|
|
|
|
|
|
|
|
|
|
// 辅助函数:从事件中获取正确的指针位置
|
|
|
|
|
|
const getPointerPosition = (e: any): Point => { |
|
|
|
|
|
const pos = stageRef.current.getPointerPosition(); |
|
|
|
|
|
return pos ? pos : { x: e.evt.clientX, y: e.evt.clientY }; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const isDragging = useRef(false); |
|
|
|
|
|
const lastPos = useRef<{ x: number; y: number } | null>(null); |
|
|
|
|
|
const dragFrame = useRef<number | null>(null); |
|
|
|
|
|
|
|
|
|
|
|
const handleMouseDown = () => { |
|
|
|
|
|
isDragging.current = true; |
|
|
|
|
|
lastPos.current = stageRef.current.getPointerPosition(); |
|
|
|
|
|
|
|
|
const handlePointerDown = (e: any) => { |
|
|
|
|
|
const point = getPointerPosition(e); |
|
|
|
|
|
const pointerId = e.evt.pointerId; |
|
|
|
|
|
pointersRef.current.set(pointerId, point); |
|
|
|
|
|
// 判断是否为触摸事件
|
|
|
|
|
|
const isTouch = e.evt.touches && e.evt.touches.length > 0; |
|
|
|
|
|
if (!isTouch) { |
|
|
|
|
|
isDraggingRef.current = true; |
|
|
|
|
|
} |
|
|
|
|
|
if (pointersRef.current.size === 1) { |
|
|
|
|
|
lastDragPosRef.current = point; |
|
|
|
|
|
} |
|
|
|
|
|
// 当检测到两个手指时,记录初始数据——固定缩放中心
|
|
|
|
|
|
if (pointersRef.current.size === 2) { |
|
|
|
|
|
const pts = Array.from(pointersRef.current.values()); |
|
|
|
|
|
const initialCenter = { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }; |
|
|
|
|
|
const initialDistance = Math.hypot(pts[1].x - pts[0].x, pts[1].y - pts[0].y); |
|
|
|
|
|
pinchDataRef.current = { |
|
|
|
|
|
initialDistance, |
|
|
|
|
|
initialScale: scale, |
|
|
|
|
|
initialOffset: offset, |
|
|
|
|
|
initialCenter, // 固定的缩放中心
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const handleMouseMove = () => { |
|
|
|
|
|
if (!isDragging.current) return; |
|
|
|
|
|
const currentPos = stageRef.current.getPointerPosition(); |
|
|
|
|
|
if (lastPos.current && currentPos) { |
|
|
|
|
|
if (!dragFrame.current) { |
|
|
|
|
|
dragFrame.current = requestAnimationFrame(() => { |
|
|
|
|
|
const last = lastPos.current!; |
|
|
|
|
|
const newOff = { |
|
|
|
|
|
x: offset.x + currentPos.x - last.x, |
|
|
|
|
|
y: offset.y + currentPos.y - last.y, |
|
|
|
|
|
|
|
|
} |
|
|
}; |
|
|
}; |
|
|
setOffset(clampOffset(newOff, scale)); |
|
|
|
|
|
lastPos.current = currentPos; |
|
|
|
|
|
dragFrame.current = null; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handlePointerMove = (e: any) => { |
|
|
|
|
|
const isTouch = e.evt.touches && e.evt.touches.length > 0; |
|
|
|
|
|
if (!isTouch && !isDraggingRef.current) { |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
const pointerId = e.evt.pointerId; |
|
|
|
|
|
const point = getPointerPosition(e); |
|
|
|
|
|
pointersRef.current.set(pointerId, point); |
|
|
|
|
|
if (pointersRef.current.size === 1) { |
|
|
|
|
|
// 单指拖拽
|
|
|
|
|
|
if (lastDragPosRef.current) { |
|
|
|
|
|
const dx = point.x - lastDragPosRef.current.x; |
|
|
|
|
|
const dy = point.y - lastDragPosRef.current.y; |
|
|
|
|
|
setOffset((prev) => clampOffset({ x: prev.x + dx, y: prev.y + dy }, scale)); |
|
|
} |
|
|
} |
|
|
|
|
|
lastDragPosRef.current = point; |
|
|
|
|
|
} else if (pointersRef.current.size >= 2 && pinchDataRef.current) { |
|
|
|
|
|
// 双指缩放:固定缩放中心,与鼠标滚轮缩放逻辑一致
|
|
|
|
|
|
const pts = Array.from(pointersRef.current.values()); |
|
|
|
|
|
const currentDistance = Math.hypot(pts[1].x - pts[0].x, pts[1].y - pts[0].y); |
|
|
|
|
|
const { initialDistance, initialScale, initialOffset, initialCenter } = pinchDataRef.current; |
|
|
|
|
|
const scaleFactor = currentDistance / initialDistance; |
|
|
|
|
|
let newScale = initialScale * scaleFactor; |
|
|
|
|
|
newScale = Math.max(minZoom * pixelPerMm, Math.min(newScale, maxZoom * pixelPerMm)); |
|
|
|
|
|
// 计算逻辑坐标 L:与鼠标滚轮缩放公式一致,固定 pointer 为初始记录的 initialCenter
|
|
|
|
|
|
const L = { |
|
|
|
|
|
x: origin.x + (initialCenter.x - (canvasCenter.x + initialOffset.x)) / initialScale, |
|
|
|
|
|
y: origin.y + (initialCenter.y - (canvasCenter.y + initialOffset.y)) / initialScale, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const handleMouseUp = () => { |
|
|
|
|
|
isDragging.current = false; |
|
|
|
|
|
if (dragFrame.current) { |
|
|
|
|
|
cancelAnimationFrame(dragFrame.current); |
|
|
|
|
|
dragFrame.current = null; |
|
|
|
|
|
|
|
|
// 计算新的 offset
|
|
|
|
|
|
const newOffset = { |
|
|
|
|
|
x: initialCenter.x - canvasCenter.x - (L.x - origin.x) * newScale, |
|
|
|
|
|
y: initialCenter.y - canvasCenter.y - (L.y - origin.y) * newScale, |
|
|
|
|
|
}; |
|
|
|
|
|
setScale(newScale); |
|
|
|
|
|
// 此处调用 clampOffset 确保缩放过程中不会超出边界
|
|
|
|
|
|
setOffset(clampOffset(newOffset, newScale)); |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const handleMouseLeave = () => { |
|
|
|
|
|
isDragging.current = false; |
|
|
|
|
|
if (dragFrame.current) { |
|
|
|
|
|
cancelAnimationFrame(dragFrame.current); |
|
|
|
|
|
dragFrame.current = null; |
|
|
|
|
|
|
|
|
const handlePointerUp = (e: any) => { |
|
|
|
|
|
const pointerId = e.evt.pointerId; |
|
|
|
|
|
pointersRef.current.delete(pointerId); |
|
|
|
|
|
const isTouch = e.evt.touches && e.evt.touches.length > 0; |
|
|
|
|
|
if (!isTouch) { |
|
|
|
|
|
isDraggingRef.current = false; |
|
|
|
|
|
} |
|
|
|
|
|
// 当手指数不足2时重置双指缩放初始数据
|
|
|
|
|
|
if (pointersRef.current.size < 2) { |
|
|
|
|
|
pinchDataRef.current = null; |
|
|
} |
|
|
} |
|
|
|
|
|
if (pointersRef.current.size === 1) { |
|
|
|
|
|
const remaining = Array.from(pointersRef.current.values())[0]; |
|
|
|
|
|
lastDragPosRef.current = remaining; |
|
|
|
|
|
} else if (pointersRef.current.size === 0) { |
|
|
|
|
|
lastDragPosRef.current = null; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handlePointerCancel = (e: any) => { |
|
|
|
|
|
handlePointerUp(e); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 鼠标滚轮缩放(仅适用于 PC)
|
|
|
const handleWheel = (e: any) => { |
|
|
const handleWheel = (e: any) => { |
|
|
e.evt.preventDefault(); |
|
|
e.evt.preventDefault(); |
|
|
const oldScale = scale; |
|
|
const oldScale = scale; |
|
|
const pointer = stageRef.current.getPointerPosition(); |
|
|
const pointer = stageRef.current.getPointerPosition(); |
|
|
|
|
|
if (!pointer) return; |
|
|
const L = { |
|
|
const L = { |
|
|
x: origin.x + (pointer.x - (canvasCenter.x + offset.x)) / oldScale, |
|
|
x: origin.x + (pointer.x - (canvasCenter.x + offset.x)) / oldScale, |
|
|
y: origin.y + (pointer.y - (canvasCenter.y + offset.y)) / oldScale, |
|
|
y: origin.y + (pointer.y - (canvasCenter.y + offset.y)) / oldScale, |
|
|
}; |
|
|
}; |
|
|
let newScale = e.evt.deltaY < 0 ? oldScale * 1.1 : oldScale / 1.1; |
|
|
let newScale = e.evt.deltaY < 0 ? oldScale * 1.1 : oldScale / 1.1; |
|
|
if (newScale < minZoom * pixelPerMm) newScale = minZoom * pixelPerMm; |
|
|
|
|
|
if (newScale > maxZoom * pixelPerMm) newScale = maxZoom * pixelPerMm; |
|
|
|
|
|
|
|
|
newScale = Math.max(minZoom * pixelPerMm, Math.min(newScale, maxZoom * pixelPerMm)); |
|
|
const newOffset = { |
|
|
const newOffset = { |
|
|
x: pointer.x - canvasCenter.x - (L.x - origin.x) * newScale, |
|
|
x: pointer.x - canvasCenter.x - (L.x - origin.x) * newScale, |
|
|
y: pointer.y - canvasCenter.y - (L.y - origin.y) * newScale, |
|
|
y: pointer.y - canvasCenter.y - (L.y - origin.y) * newScale, |
|
@ -281,69 +318,27 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
setOffset(clampOffset(newOffset, newScale)); |
|
|
setOffset(clampOffset(newOffset, newScale)); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const lastTouchDistance = useRef<number | null>(null); |
|
|
|
|
|
const handleTouchStart = (e: any) => { |
|
|
|
|
|
const touches = e.evt.touches; |
|
|
|
|
|
if (touches && touches.length === 2) { |
|
|
|
|
|
e.evt.preventDefault(); |
|
|
|
|
|
const [t1, t2] = touches; |
|
|
|
|
|
const dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY); |
|
|
|
|
|
lastTouchDistance.current = dist; |
|
|
|
|
|
} else if (touches && touches.length === 1) { |
|
|
|
|
|
isDragging.current = true; |
|
|
|
|
|
lastPos.current = { x: touches[0].clientX, y: touches[0].clientY }; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleTouchMove = (e: any) => { |
|
|
|
|
|
const touches = e.evt.touches; |
|
|
|
|
|
if (touches && touches.length === 2) { |
|
|
|
|
|
e.evt.preventDefault(); |
|
|
|
|
|
const [t1, t2] = touches; |
|
|
|
|
|
const dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY); |
|
|
|
|
|
if (lastTouchDistance.current) { |
|
|
|
|
|
const delta = dist / lastTouchDistance.current; |
|
|
|
|
|
let newScale = scale * delta; |
|
|
|
|
|
if (newScale < minZoom * pixelPerMm) newScale = minZoom * pixelPerMm; |
|
|
|
|
|
if (newScale > maxZoom * pixelPerMm) newScale = maxZoom * pixelPerMm; |
|
|
|
|
|
const center = { |
|
|
|
|
|
x: (t1.clientX + t2.clientX) / 2, |
|
|
|
|
|
y: (t1.clientY + t2.clientY) / 2, |
|
|
|
|
|
}; |
|
|
|
|
|
const L = { |
|
|
|
|
|
x: origin.x + (center.x - (canvasCenter.x + offset.x)) / scale, |
|
|
|
|
|
y: origin.y + (center.y - (canvasCenter.y + offset.y)) / scale, |
|
|
|
|
|
|
|
|
// 辅助函数:将逻辑坐标转换为屏幕坐标
|
|
|
|
|
|
const transform = (pt: Point | null): { x: number; y: number } => { |
|
|
|
|
|
if (!pt) return { x: 0, y: 0 }; |
|
|
|
|
|
return { |
|
|
|
|
|
x: canvasCenter.x + offset.x + (pt.x - origin.x) * scale, |
|
|
|
|
|
y: canvasCenter.y + offset.y + (pt.y - origin.y) * scale, |
|
|
}; |
|
|
}; |
|
|
const newOffset = { |
|
|
|
|
|
x: center.x - canvasCenter.x - (L.x - origin.x) * newScale, |
|
|
|
|
|
y: center.y - canvasCenter.y - (L.y - origin.y) * newScale, |
|
|
|
|
|
}; |
|
|
|
|
|
setScale(newScale); |
|
|
|
|
|
setOffset(clampOffset(newOffset, newScale)); |
|
|
|
|
|
lastTouchDistance.current = dist; |
|
|
|
|
|
} |
|
|
|
|
|
} else if (touches && touches.length === 1 && isDragging.current) { |
|
|
|
|
|
const t = touches[0]; |
|
|
|
|
|
const pos = { x: t.clientX, y: t.clientY }; |
|
|
|
|
|
if (lastPos.current) { |
|
|
|
|
|
setOffset((prev) => |
|
|
|
|
|
clampOffset( |
|
|
|
|
|
{ |
|
|
|
|
|
x: prev.x + pos.x - lastPos.current!.x, |
|
|
|
|
|
y: prev.y + pos.y - lastPos.current!.y, |
|
|
|
|
|
}, |
|
|
|
|
|
scale |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
lastPos.current = pos; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const handleTouchEnd = () => { |
|
|
|
|
|
isDragging.current = false; |
|
|
|
|
|
lastTouchDistance.current = null; |
|
|
|
|
|
lastPos.current = null; |
|
|
|
|
|
|
|
|
const clampOffset = (newOffset: { x: number; y: number }, currentScale: number) => { |
|
|
|
|
|
const left = canvasCenter.x + newOffset.x + (logicalExtent.minX - origin.x) * currentScale; |
|
|
|
|
|
const right = canvasCenter.x + newOffset.x + (logicalExtent.maxX - origin.x) * currentScale; |
|
|
|
|
|
const top = canvasCenter.y + newOffset.y + (logicalExtent.minY - origin.y) * currentScale; |
|
|
|
|
|
const bottom = canvasCenter.y + newOffset.y + (logicalExtent.maxY - origin.y) * currentScale; |
|
|
|
|
|
let clampedX = newOffset.x; |
|
|
|
|
|
let clampedY = newOffset.y; |
|
|
|
|
|
if (left > 0) clampedX -= left; |
|
|
|
|
|
if (right < width) clampedX += width - right; |
|
|
|
|
|
if (top > 0) clampedY -= top; |
|
|
|
|
|
if (bottom < height) clampedY += height - bottom; |
|
|
|
|
|
return { x: clampedX, y: clampedY }; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const renderGridAndAxes = () => { |
|
|
const renderGridAndAxes = () => { |
|
@ -352,45 +347,25 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
const p1 = transform({ x, y: logicalExtent.minY }); |
|
|
const p1 = transform({ x, y: logicalExtent.minY }); |
|
|
const p2 = transform({ x, y: logicalExtent.maxY }); |
|
|
const p2 = transform({ x, y: logicalExtent.maxY }); |
|
|
lines.push( |
|
|
lines.push( |
|
|
<Line |
|
|
|
|
|
key={`v-${x}`} |
|
|
|
|
|
points={[p1.x, p1.y, p2.x, p2.y]} |
|
|
|
|
|
stroke="#eee" |
|
|
|
|
|
strokeWidth={1} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<Line key={`v-${x}`} points={[p1.x, p1.y, p2.x, p2.y]} stroke="#eee" strokeWidth={1} /> |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
for (let y = logicalExtent.minY; y <= logicalExtent.maxY; y += gridStep) { |
|
|
for (let y = logicalExtent.minY; y <= logicalExtent.maxY; y += gridStep) { |
|
|
const p1 = transform({ x: logicalExtent.minX, y }); |
|
|
const p1 = transform({ x: logicalExtent.minX, y }); |
|
|
const p2 = transform({ x: logicalExtent.maxX, y }); |
|
|
const p2 = transform({ x: logicalExtent.maxX, y }); |
|
|
lines.push( |
|
|
lines.push( |
|
|
<Line |
|
|
|
|
|
key={`h-${y}`} |
|
|
|
|
|
points={[p1.x, p1.y, p2.x, p2.y]} |
|
|
|
|
|
stroke="#eee" |
|
|
|
|
|
strokeWidth={1} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<Line key={`h-${y}`} points={[p1.x, p1.y, p2.x, p2.y]} stroke="#eee" strokeWidth={1} /> |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
const xAxisStart = transform({ x: logicalExtent.minX, y: 0 }); |
|
|
const xAxisStart = transform({ x: logicalExtent.minX, y: 0 }); |
|
|
const xAxisEnd = transform({ x: logicalExtent.maxX, y: 0 }); |
|
|
const xAxisEnd = transform({ x: logicalExtent.maxX, y: 0 }); |
|
|
lines.push( |
|
|
lines.push( |
|
|
<Line |
|
|
|
|
|
key="x-axis" |
|
|
|
|
|
points={[xAxisStart.x, xAxisStart.y, xAxisEnd.x, xAxisEnd.y]} |
|
|
|
|
|
stroke="gray" |
|
|
|
|
|
strokeWidth={2} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<Line key="x-axis" points={[xAxisStart.x, xAxisStart.y, xAxisEnd.x, xAxisEnd.y]} stroke="gray" strokeWidth={2} /> |
|
|
); |
|
|
); |
|
|
const yAxisStart = transform({ x: 0, y: logicalExtent.minY }); |
|
|
const yAxisStart = transform({ x: 0, y: logicalExtent.minY }); |
|
|
const yAxisEnd = transform({ x: 0, y: logicalExtent.maxY }); |
|
|
const yAxisEnd = transform({ x: 0, y: logicalExtent.maxY }); |
|
|
lines.push( |
|
|
lines.push( |
|
|
<Line |
|
|
|
|
|
key="y-axis" |
|
|
|
|
|
points={[yAxisStart.x, yAxisStart.y, yAxisEnd.x, yAxisEnd.y]} |
|
|
|
|
|
stroke="gray" |
|
|
|
|
|
strokeWidth={2} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<Line key="y-axis" points={[yAxisStart.x, yAxisStart.y, yAxisEnd.x, yAxisEnd.y]} stroke="gray" strokeWidth={2} /> |
|
|
); |
|
|
); |
|
|
return lines; |
|
|
return lines; |
|
|
}; |
|
|
}; |
|
@ -400,31 +375,16 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
const minSpacing = 30; |
|
|
const minSpacing = 30; |
|
|
const dynamicXInterval = Math.max(coordinateInterval, Math.ceil(minSpacing / scale)); |
|
|
const dynamicXInterval = Math.max(coordinateInterval, Math.ceil(minSpacing / scale)); |
|
|
const dynamicYInterval = Math.max(coordinateInterval, Math.ceil(minSpacing / scale)); |
|
|
const dynamicYInterval = Math.max(coordinateInterval, Math.ceil(minSpacing / scale)); |
|
|
|
|
|
|
|
|
for (let x = logicalExtent.minX; x <= logicalExtent.maxX; x += dynamicXInterval) { |
|
|
for (let x = logicalExtent.minX; x <= logicalExtent.maxX; x += dynamicXInterval) { |
|
|
const pos = transform({ x, y: 0 }); |
|
|
const pos = transform({ x, y: 0 }); |
|
|
texts.push( |
|
|
texts.push( |
|
|
<Text |
|
|
|
|
|
key={`coord-x-${x}`} |
|
|
|
|
|
x={pos.x - 10} |
|
|
|
|
|
y={height - 20} |
|
|
|
|
|
text={x.toFixed(0)} |
|
|
|
|
|
fontSize={12} |
|
|
|
|
|
fill="black" |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<Text key={`coord-x-${x}`} x={pos.x - 10} y={height - 20} text={x.toFixed(0)} fontSize={12} fill="black" /> |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
for (let y = logicalExtent.minY; y <= logicalExtent.maxY; y += dynamicYInterval) { |
|
|
for (let y = logicalExtent.minY; y <= logicalExtent.maxY; y += dynamicYInterval) { |
|
|
const pos = transform({ x: 0, y }); |
|
|
const pos = transform({ x: 0, y }); |
|
|
texts.push( |
|
|
texts.push( |
|
|
<Text |
|
|
|
|
|
key={`coord-y-${y}`} |
|
|
|
|
|
x={5} |
|
|
|
|
|
y={pos.y - 6} |
|
|
|
|
|
text={y.toFixed(0)} |
|
|
|
|
|
fontSize={12} |
|
|
|
|
|
fill="black" |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<Text key={`coord-y-${y}`} x={5} y={pos.y - 6} text={y.toFixed(0)} fontSize={12} fill="black" /> |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
return texts; |
|
|
return texts; |
|
@ -474,9 +434,7 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
startAngle: normalize(startAngle), |
|
|
startAngle: normalize(startAngle), |
|
|
endAngle: normalize(endAngle), |
|
|
endAngle: normalize(endAngle), |
|
|
anticlockwise: |
|
|
anticlockwise: |
|
|
(normalize(endAngle) - normalize(startAngle) + 2 * Math.PI) % |
|
|
|
|
|
(2 * Math.PI) > |
|
|
|
|
|
Math.PI, |
|
|
|
|
|
|
|
|
(normalize(endAngle) - normalize(startAngle) + 2 * Math.PI) % (2 * Math.PI) > Math.PI, |
|
|
}; |
|
|
}; |
|
|
return ( |
|
|
return ( |
|
|
<Shape |
|
|
<Shape |
|
@ -569,18 +527,8 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
const pB = transform(item.pointB); |
|
|
const pB = transform(item.pointB); |
|
|
return ( |
|
|
return ( |
|
|
<React.Fragment key={`analysis-${idx}`}> |
|
|
<React.Fragment key={`analysis-${idx}`}> |
|
|
<Line |
|
|
|
|
|
points={[pA.x, pA.y, pB.x, pB.y]} |
|
|
|
|
|
stroke="red" |
|
|
|
|
|
strokeWidth={1} |
|
|
|
|
|
/> |
|
|
|
|
|
<Text |
|
|
|
|
|
x={pA.x - 15} |
|
|
|
|
|
y={pA.y - 15} |
|
|
|
|
|
text={item.describe} |
|
|
|
|
|
fontSize={14} |
|
|
|
|
|
fill="black" |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<Line points={[pA.x, pA.y, pB.x, pB.y]} stroke="red" strokeWidth={1} /> |
|
|
|
|
|
<Text x={pA.x - 15} y={pA.y - 15} text={item.describe} fontSize={14} fill="black" /> |
|
|
</React.Fragment> |
|
|
</React.Fragment> |
|
|
); |
|
|
); |
|
|
}); |
|
|
}); |
|
@ -600,14 +548,11 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp |
|
|
ref={stageRef} |
|
|
ref={stageRef} |
|
|
width={width} |
|
|
width={width} |
|
|
height={height} |
|
|
height={height} |
|
|
onMouseDown={handleMouseDown} |
|
|
|
|
|
onMouseMove={handleMouseMove} |
|
|
|
|
|
onMouseUp={handleMouseUp} |
|
|
|
|
|
onMouseLeave={handleMouseLeave} |
|
|
|
|
|
|
|
|
onPointerDown={handlePointerDown} |
|
|
|
|
|
onPointerMove={handlePointerMove} |
|
|
|
|
|
onPointerUp={handlePointerUp} |
|
|
|
|
|
onPointerCancel={handlePointerCancel} |
|
|
onWheel={handleWheel} |
|
|
onWheel={handleWheel} |
|
|
onTouchStart={handleTouchStart} |
|
|
|
|
|
onTouchMove={handleTouchMove} |
|
|
|
|
|
onTouchEnd={handleTouchEnd} |
|
|
|
|
|
style={{ background: "#fff" }} |
|
|
style={{ background: "#fff" }} |
|
|
> |
|
|
> |
|
|
<Layer> |
|
|
<Layer> |
|
|