diff --git a/src/pages/measure/components/MeasureAction.tsx b/src/pages/measure/components/MeasureAction.tsx index 614568b..e809712 100644 --- a/src/pages/measure/components/MeasureAction.tsx +++ b/src/pages/measure/components/MeasureAction.tsx @@ -1,9 +1,10 @@ import { Button, Checkbox, CheckboxProps, Radio, RadioChangeEvent, message } from "antd"; import { useState, useEffect } from "react"; import { useNavigate } from "react-router"; -import SectionalView from "./SectionalView"; import { startMeasurement } from "../../../services/measure/analysis" import { createWebSocket, sharedWsUrl } from "../../../services/socket"; +import GridLayer from "./graph/GridLayer"; +import StandardLayer from "./graph/StandardLayer"; export default function MeasureAction() { const navigate = useNavigate(); @@ -41,8 +42,11 @@ export default function MeasureAction() { } return (
-
- +
+ +
+ +

测量步骤

diff --git a/src/pages/measure/components/SectionalView.tsx b/src/pages/measure/components/SectionalView.tsx deleted file mode 100644 index eb03704..0000000 --- a/src/pages/measure/components/SectionalView.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { useCallback, useEffect, useRef } from "react"; - -const primaryLineColor = "rgb(203,213,245)"; -const subLineColor = "rgb(226,231,232)"; - -export default function SectionalView(props: { - width: number; - height: number; - leftPadding: number; - rightPadding: number; - topPadding: number; - bottomPadding: number; - columns: number; - rows: number; - cellNum: number; -}) { - const xStartPx = props.leftPadding; - const xEndPx = props.width - props.rightPadding; - const xStepPx = (props.width - props.leftPadding - props.rightPadding) / props.columns; - const xUnitPx = xStepPx / props.cellNum; - const yStartPx = props.topPadding; - const yEndPx = props.height - props.bottomPadding; - const yStepPx = (props.height - props.topPadding - props.bottomPadding) / props.rows; - const yUnitPx = yStepPx / props.cellNum; - - const canvasRef = useRef(null); - const drawGrid = useCallback((ctx: CanvasRenderingContext2D) => { - ctx.beginPath(); - ctx.strokeStyle = subLineColor; - for (let i = 1; i < props.columns * props.cellNum; ++i) { - if (i % props.cellNum === 0) continue; - ctx.moveTo(xStartPx + xUnitPx * i, yStartPx); - ctx.lineTo(xStartPx + xUnitPx * i, yEndPx); - } - for (let j = 1; j < props.rows * props.cellNum; ++j) { - if (j % props.cellNum === 0) continue; - ctx.moveTo(xStartPx, yStartPx + yUnitPx * j); - ctx.lineTo(xEndPx, yStartPx + yUnitPx * j); - } - ctx.stroke(); - - ctx.beginPath(); - ctx.strokeStyle = primaryLineColor; - for (let i = 0; i <= props.columns; ++i) { - ctx.moveTo(xStartPx + xStepPx * i, yStartPx); - ctx.lineTo(xStartPx + xStepPx * i, yEndPx); - } - for (let j = 0; j <= props.rows; ++j) { - ctx.moveTo(xStartPx, yStartPx + yStepPx * j); - ctx.lineTo(xEndPx, yStartPx + yStepPx * j); - } - ctx.stroke(); - },[props.cellNum, props.columns, props.rows, xEndPx, xStartPx, xStepPx, xUnitPx, yEndPx, yStartPx, yStepPx, yUnitPx]) - - useEffect(() => { - // 获取canvas的2D绘图上下文 - const canvas = canvasRef.current; - if (!canvas) return; - - const context = canvas.getContext("2d"); - if (!context) return; - // 使用context对象进行绘图 - drawGrid(context); - }, [drawGrid]); - - - return ( -
- -
- ); -} diff --git a/src/pages/measure/components/graph/AreaLayer.tsx b/src/pages/measure/components/graph/AreaLayer.tsx new file mode 100644 index 0000000..f7bcede --- /dev/null +++ b/src/pages/measure/components/graph/AreaLayer.tsx @@ -0,0 +1,31 @@ +import { useCallback, useEffect, useRef } from "react"; + +export default function AreaLayer(props: { + width: number; + height: number; + leftPadding: number; + rightPadding: number; + topPadding: number; + bottomPadding: number; +}) { + const canvasRef = useRef(null); + const draw = useCallback((ctx: CanvasRenderingContext2D) => { + // 使用context对象进行绘图 + ctx.fillStyle = "skyblue"; // 设置填充颜色 + ctx.fillRect(50, 50, 150, 100); // 绘制一个矩形 + }, []); + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const context = canvas.getContext("2d"); + if (!context) return; + draw(context); + }, [draw]); + + return ( +
+ +
+ ); +} diff --git a/src/pages/measure/components/graph/GridLayer.tsx b/src/pages/measure/components/graph/GridLayer.tsx new file mode 100644 index 0000000..fb36c71 --- /dev/null +++ b/src/pages/measure/components/graph/GridLayer.tsx @@ -0,0 +1,88 @@ +import { useCallback, useEffect, useRef } from "react"; + +const primaryLineColor = "rgb(203,213,245)"; +const subLineColor = "rgb(226,231,232)"; + +export default function GridLayer(props: { + width: number; + height: number; + leftPadding: number; + rightPadding: number; + topPadding: number; + bottomPadding: number; + columns: number; + rows: number; + colCellNum: number; + rowCellNum: number; +}) { + const xStartPx = props.leftPadding; + const xEndPx = props.width - props.rightPadding; + const xStepPx = (props.width - props.leftPadding - props.rightPadding) / props.columns; + const xUnitPx = xStepPx / props.colCellNum; + const yStartPx = props.topPadding; + const yEndPx = props.height - props.bottomPadding; + const yStepPx = (props.height - props.topPadding - props.bottomPadding) / props.rows; + const yUnitPx = yStepPx / props.rowCellNum; + + const canvasRef = useRef(null); + const drawGrid = useCallback( + (ctx: CanvasRenderingContext2D) => { + ctx.beginPath(); + ctx.strokeStyle = subLineColor; + for (let i = 1; i < props.columns * props.colCellNum; ++i) { + if (i % props.colCellNum === 0) continue; + ctx.moveTo(xStartPx + xUnitPx * i, yStartPx); + ctx.lineTo(xStartPx + xUnitPx * i, yEndPx); + } + for (let j = 1; j < props.rows * props.rowCellNum; ++j) { + if (j % props.rowCellNum === 0) continue; + ctx.moveTo(xStartPx, yStartPx + yUnitPx * j); + ctx.lineTo(xEndPx, yStartPx + yUnitPx * j); + } + ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle = primaryLineColor; + for (let i = 0; i <= props.columns; ++i) { + ctx.moveTo(xStartPx + xStepPx * i, yStartPx); + ctx.lineTo(xStartPx + xStepPx * i, yEndPx); + } + for (let j = 0; j <= props.rows; ++j) { + ctx.moveTo(xStartPx, yStartPx + yStepPx * j); + ctx.lineTo(xEndPx, yStartPx + yStepPx * j); + } + ctx.stroke(); + }, + [ + props.colCellNum, + props.columns, + props.rowCellNum, + props.rows, + xEndPx, + xStartPx, + xStepPx, + xUnitPx, + yEndPx, + yStartPx, + yStepPx, + yUnitPx, + ] + ); + + useEffect(() => { + // 获取canvas的2D绘图上下文 + const canvas = canvasRef.current; + if (!canvas) return; + + const context = canvas.getContext("2d"); + if (!context) return; + // 使用context对象进行绘图 + drawGrid(context); + }, [drawGrid]); + + return ( +
+ +
+ ); +} diff --git a/src/pages/measure/components/graph/MarkLayer.tsx b/src/pages/measure/components/graph/MarkLayer.tsx new file mode 100644 index 0000000..28bb3d8 --- /dev/null +++ b/src/pages/measure/components/graph/MarkLayer.tsx @@ -0,0 +1,31 @@ +import { useCallback, useEffect, useRef } from "react"; + +export default function MarkLayer(props: { + width: number; + height: number; + leftPadding: number; + rightPadding: number; + topPadding: number; + bottomPadding: number; +}) { + const canvasRef = useRef(null); + const draw = useCallback((ctx: CanvasRenderingContext2D) => { + // 使用context对象进行绘图 + ctx.fillStyle = "skyblue"; // 设置填充颜色 + ctx.fillRect(50, 50, 150, 100); // 绘制一个矩形 + }, []); + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const context = canvas.getContext("2d"); + if (!context) return; + draw(context); + }, [draw]); + + return ( +
+ +
+ ); +} diff --git a/src/pages/measure/components/graph/RealtimeLayer.tsx b/src/pages/measure/components/graph/RealtimeLayer.tsx new file mode 100644 index 0000000..4d4c794 --- /dev/null +++ b/src/pages/measure/components/graph/RealtimeLayer.tsx @@ -0,0 +1,31 @@ +import { useCallback, useEffect, useRef } from "react"; + +export default function RealtimeLayer(props: { + width: number; + height: number; + leftPadding: number; + rightPadding: number; + topPadding: number; + bottomPadding: number; +}) { + const canvasRef = useRef(null); + const draw = useCallback((ctx: CanvasRenderingContext2D) => { + // 使用context对象进行绘图 + ctx.fillStyle = "skyblue"; // 设置填充颜色 + ctx.fillRect(50, 50, 150, 100); // 绘制一个矩形 + }, []); + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const context = canvas.getContext("2d"); + if (!context) return; + draw(context); + }, [draw]); + + return ( +
+ +
+ ); +} diff --git a/src/pages/measure/components/graph/StandardLayer.tsx b/src/pages/measure/components/graph/StandardLayer.tsx new file mode 100644 index 0000000..d6bed8f --- /dev/null +++ b/src/pages/measure/components/graph/StandardLayer.tsx @@ -0,0 +1,209 @@ +import { useCallback, useEffect, useRef } from "react"; +import { calculateCircleAbove, calculateCircleBelow } from "../../../../utils"; + +const unitPx = 8; + +const pointsR: [number, number][] = [ + [0, 0], + [9.949007022412, 0.1650166186941], + [25.35, 2.184814802617], + [35.4, 14.20034968551], + [36.31679456414, 32.538841443], + [32.90417417089, 37.53190928715], + [20.0, 41.8333134842], +]; +const pointsL: [number, number][] = [ + [-20.0, 41.83331348425], + [-32.90417417089, 37.53190928715], + [-36.31679456414, 32.538841443], + [-35.4, 14.20034968551], + [-25.35, 2.184814802617], + [-9.949007022412, 0.1650166186941], + [0, 0], +]; +const pointsRight = pointsR.map(p => [p[0] * unitPx, p[1] * unitPx]); +const pointsLeft = pointsL.map(p => [p[0] * unitPx, p[1] * unitPx]); + +export default function StandardLayer(props: { + width: number; + height: number; + leftPadding: number; + rightPadding: number; + topPadding: number; + bottomPadding: number; + columns: number; + rows: number; +}) { + const xStartPx = props.leftPadding; + const xEndPx = props.width - props.rightPadding; + const xStepPx = (props.width - props.leftPadding - props.rightPadding) / props.columns; + const yStartPx = props.topPadding; + const yEndPx = props.height - props.bottomPadding; + const yStepPx = (props.height - props.topPadding - props.bottomPadding) / props.rows; + + const canvasRef = useRef(null); + + const draw = useCallback( + (ctx: CanvasRenderingContext2D) => { + ctx.strokeStyle = "#999"; + // 偏移原点 + const xOffset = (xEndPx - xStartPx) / 2; + const yOffset = yStepPx * 2; + const yMax = yEndPx - yStartPx - yOffset; + ctx.translate(xStartPx + xOffset, yStartPx + yOffset); + // 绘制原点交叉线 + ctx.moveTo(-xOffset, 0); + ctx.lineTo(xOffset, 0); + ctx.moveTo(0, -yOffset); + ctx.lineTo(0, yMax); + ctx.stroke(); + + // 绘制x轴y轴 单位数值 + ctx.beginPath(); + ctx.fillStyle = "#333333"; + ctx.textAlign = "center"; + ctx.font = "normal 14px system"; + for (let index = -4; index < 5; index++) { + ctx.fillText((index * 10).toString(), xStepPx * index, yMax + 20); + } + for (let index = -1; index < 5; index++) { + ctx.fillText((-index * 10).toString(), -xOffset - (index > 0 ? 18 : 14), yStepPx * index + 4); + } + + // 绘制标准线 + ctx.beginPath(); + ctx.strokeStyle = "red"; + // ---- 右侧 + let point0 = pointsRight[0]; + let point1 = pointsRight[1]; + ctx.moveTo(point0[0], point0[1]); + let param = calculateCircleBelow(point0[0], point0[1], point1[0], point1[1], 300 * unitPx); + console.log(param); + ctx.arc( + param.center.x, + param.center.y, + 300 * unitPx, + param.anglesForCenter.startAngle, + param.anglesForCenter.endAngle + ); + + point0 = pointsRight[1]; + point1 = pointsRight[2]; + ctx.moveTo(point0[0], point0[1]); + param = calculateCircleBelow(point0[0], point0[1], point1[0], point1[1], 80 * unitPx); + console.log(param); + ctx.arc( + param.center.x, + param.center.y, + 80 * unitPx, + param.anglesForCenter.startAngle, + param.anglesForCenter.endAngle + ); + + point0 = pointsRight[2]; + point1 = pointsRight[3]; + ctx.moveTo(point0[0], point0[1]); + param = calculateCircleBelow(point0[0], point0[1], point1[0], point1[1], 13 * unitPx); + console.log(param); + ctx.arc( + param.center.x, + param.center.y, + 13 * unitPx, + param.anglesForCenter.startAngle, + param.anglesForCenter.endAngle + ); + + point0 = pointsRight[3]; + point1 = pointsRight[4]; + ctx.moveTo(point0[0], point0[1]); + ctx.lineTo(point1[0], point1[1]); + + point0 = pointsRight[4]; + point1 = pointsRight[5]; + ctx.moveTo(point0[0], point0[1]); + param = calculateCircleAbove(point0[0], point0[1], point1[0], point1[1], 5 * unitPx); + console.log(param); + ctx.arc(param.center.x, param.center.y, 5 * unitPx, param.anglesForCenter.startAngle, param.anglesForCenter.endAngle); + + point0 = pointsRight[5]; + point1 = pointsRight[6]; + ctx.moveTo(point0[0], point0[1]); + ctx.lineTo(point1[0], point1[1]); + + // ---- 左侧 + point0 = pointsLeft[0]; + point1 = pointsLeft[1]; + ctx.moveTo(point0[0], point0[1]); + ctx.lineTo(point1[0], point1[1]); + + point0 = pointsLeft[1]; + point1 = pointsLeft[2]; + ctx.moveTo(point0[0], point0[1]); + param = calculateCircleAbove(point0[0], point0[1], point1[0], point1[1], 5 * unitPx); + console.log(param); + ctx.arc(param.center.x, param.center.y, 5 * unitPx, param.anglesForCenter.startAngle, param.anglesForCenter.endAngle); + + point0 = pointsLeft[2]; + point1 = pointsLeft[3]; + ctx.moveTo(point0[0], point0[1]); + ctx.lineTo(point1[0], point1[1]); + + point0 = pointsLeft[3]; + point1 = pointsLeft[4]; + ctx.moveTo(point0[0], point0[1]); + param = calculateCircleBelow(point0[0], point0[1], point1[0], point1[1], 13 * unitPx); + console.log(param); + ctx.arc( + param.center.x, + param.center.y, + 13 * unitPx, + param.anglesForCenter.startAngle, + param.anglesForCenter.endAngle + ); + + point0 = pointsLeft[4]; + point1 = pointsLeft[5]; + ctx.moveTo(point0[0], point0[1]); + param = calculateCircleBelow(point0[0], point0[1], point1[0], point1[1], 80 * unitPx); + console.log(param); + ctx.arc( + param.center.x, + param.center.y, + 80 * unitPx, + param.anglesForCenter.startAngle, + param.anglesForCenter.endAngle + ); + + point0 = pointsLeft[5]; + point1 = pointsLeft[6]; + ctx.moveTo(point0[0], point0[1]); + param = calculateCircleBelow(point0[0], point0[1], point1[0], point1[1], 300 * unitPx); + console.log(param); + ctx.arc( + param.center.x, + param.center.y, + 300 * unitPx, + param.anglesForCenter.startAngle, + param.anglesForCenter.endAngle + ); + + ctx.stroke(); + // ctx.fillRect(100, 100, 150, 100); // 绘制一个矩形 + }, + [xEndPx, xStartPx, xStepPx, yEndPx, yStartPx, yStepPx] + ); + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const context = canvas.getContext("2d"); + if (!context) return; + draw(context); + }, [draw]); + + return ( +
+ +
+ ); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 1c1eb9a..6c3a9b8 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,3 +3,63 @@ export function formatRemainTime(seconds: number) { const sec = (seconds % 60).toFixed(); return min.padStart(2, "0") + ":" + sec.padStart(2, "0"); } + +export function calculateCircleInfo(x1: number, y1: number, x2: number, y2: number, radius: number) { + // 计算P1到P2的中点 + const xm = (x1 + x2) / 2; + const ym = (y1 + y2) / 2; + + // P1到P2的距离的一半 + const dx = x2 - x1; + const dy = y2 - y1; + const d = Math.sqrt(dx * dx + dy * dy) / 2; + + // 如果距离大于直径,则无法形成圆 + if (d > radius) { + throw new Error("Given points are too far apart for the given radius."); + } + + // 计算垂直平分线的方向向量 + const len = Math.sqrt(radius * radius - d * d); + const nx = dy / (2 * d); + const ny = -dx / (2 * d); + + // 计算两个可能的圆心 + const cx1 = xm + nx * len; + const cy1 = ym + ny * len; + const cx2 = xm - nx * len; + const cy2 = ym - ny * len; + + // 使用atan2计算起始角和结束角 + function calculateAngle(cx: number, cy: number, px: number, py: number) { + return Math.atan2(py - cy, px - cx); + } + + const startAngle1 = calculateAngle(cx1, cy1, x1, y1); + const endAngle1 = calculateAngle(cx1, cy1, x2, y2); + const startAngle2 = calculateAngle(cx2, cy2, x1, y1); + const endAngle2 = calculateAngle(cx2, cy2, x2, y2); + + return [ + { center: { x: cx1, y: cy1 }, anglesForCenter: { startAngle: startAngle1, endAngle: endAngle1 } }, + { center: { x: cx2, y: cy2 }, anglesForCenter: { startAngle: startAngle2, endAngle: endAngle2 } }, + ]; +} + +export type CenterAndAngle = ReturnType; +export function calculateCircleBelow(x1: number, y1: number, x2: number, y2: number, radius: number) { + const results = calculateCircleInfo(x1, y1, x2, y2, radius); + if (results[0].center.y > results[1].center.y) { + return results[0]; + } else { + return results[1]; + } +} +export function calculateCircleAbove(x1: number, y1: number, x2: number, y2: number, radius: number) { + const results = calculateCircleInfo(x1, y1, x2, y2, radius); + if (results[0].center.y < results[1].center.y) { + return results[0]; + } else { + return results[1]; + } +}