LiLongLong 5 months ago
parent
commit
0d3cb9392e
  1. 54
      src/pages/measure/components/MeasureAction.tsx
  2. 109
      src/pages/measure/components/graph/MarkLayer.tsx
  3. 126
      src/pages/measure/components/graph/RealtimeLayer.tsx
  4. 59
      src/pages/measure/components/graph/StandardLayer.tsx
  5. 33
      src/services/socket.ts
  6. 20
      src/services/wsTypes.ts
  7. 26
      src/utils/index.ts
  8. 1503
      src/utils/measure.json

54
src/pages/measure/components/MeasureAction.tsx

@ -5,6 +5,8 @@ import { startMeasurement } from "../../../services/measure/analysis"
import { createWebSocket, sharedWsUrl } from "../../../services/socket";
import GridLayer from "./graph/GridLayer";
import StandardLayer from "./graph/StandardLayer";
import RealtimeLayer from "./graph/RealtimeLayer";
import MarkLayer from "./graph/MarkLayer";
export default function MeasureAction() {
const navigate = useNavigate();
@ -40,12 +42,56 @@ export default function MeasureAction() {
}
return (
<div className="flex h-full ">
<div className="flex-none border relative">
<GridLayer width={840} height={600} leftPadding={30} rightPadding={10} topPadding={10} bottomPadding={30} columns={10} rows={7} colCellNum={1} rowCellNum={2} />
<div className="flex-none relative">
<GridLayer
width={840}
height={600}
leftPadding={30}
rightPadding={10}
topPadding={10}
bottomPadding={30}
columns={10}
rows={7}
colCellNum={1}
rowCellNum={2}
/>
<div className="absolute top-0">
<StandardLayer width={840} height={600} leftPadding={30} rightPadding={10} topPadding={10} bottomPadding={30} columns={10} rows={7} />
<StandardLayer
width={840}
height={600}
leftPadding={30}
rightPadding={10}
topPadding={10}
bottomPadding={30}
columns={10}
rows={7}
/>
</div>
</div>
<div className="absolute top-0">
<RealtimeLayer
width={840}
height={600}
leftPadding={30}
rightPadding={10}
topPadding={10}
bottomPadding={30}
columns={10}
rows={7}
/>
</div>
<div className="absolute top-0">
<MarkLayer
width={840}
height={600}
leftPadding={30}
rightPadding={10}
topPadding={10}
bottomPadding={30}
columns={10}
rows={7}
/>
</div>
</div>
<div className="w-[300px] flex-none py-6">
<h1 className="font-medium text-xl text-center"></h1>
<section className="flex flex-col items-center gap-4 mt-6 border-t border-[#D8D8D8] py-4">

109
src/pages/measure/components/graph/MarkLayer.tsx

@ -1,31 +1,88 @@
import { useCallback, useEffect, useRef } from "react";
import { calculatePointOnCircle, findSymmetricPoint } from "../../../../utils";
const marks = [
{ x: 9.949007022412, y: 0.1650166186941, degree: -80 },
{ x: 25.35, y: 2.184814802617, degree: -60 },
{ x: -9.949007022412, y: 0.1650166186941, degree: -100 },
{ x: -25.35, y: 2.184814802617, degree: -120 },
];
export default function MarkLayer(props: {
width: number;
height: number;
leftPadding: number;
rightPadding: number;
topPadding: number;
bottomPadding: number;
width: number;
height: number;
leftPadding: number;
rightPadding: number;
topPadding: number;
bottomPadding: number;
columns: number;
rows: number;
}) {
const canvasRef = useRef<HTMLCanvasElement | null>(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 (
<div>
<canvas ref={canvasRef} width={props.width} height={props.height}></canvas>
</div>
);
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 unitPx = xStepPx / 10;
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const calcPoints = useCallback(
(arr: typeof marks) => {
return arr.map(p => {
const p1 = calculatePointOnCircle(p.x * unitPx, p.y * unitPx, xStepPx / 2, p.degree);
const p2 = findSymmetricPoint(p1.x, p1.y, p.x * unitPx, p.y * unitPx);
// 角度文本,向外偏移
const p3 = calculatePointOnCircle(p.x * unitPx, p.y * unitPx, xStepPx / 2 + 10, p.degree);
return [p1, p2, p3];
});
},
[unitPx, xStepPx]
);
const draw = useCallback(
(ctx: CanvasRenderingContext2D) => {
// 偏移原点
const xOffset = (xEndPx - xStartPx) / 2;
const yOffset = yStepPx * 2;
ctx.translate(xStartPx + xOffset, yStartPx + yOffset);
ctx.fillStyle = "#333333";
ctx.textAlign = "center";
ctx.font = "normal 14px system";
const lines = calcPoints(marks);
for (let idx = 0; idx < lines.length; idx++) {
const line = lines[idx];
ctx.moveTo(line[0].x, line[0].y);
ctx.lineTo(line[1].x, line[1].y);
ctx.save();
ctx.translate(line[2].x, line[2].y);
ctx.rotate(((marks[idx].degree + 90) * Math.PI) / 180);
ctx.fillText(`${-marks[idx].degree}°`, 0, 0);
ctx.restore();
}
ctx.stroke();
// ctx.fillStyle = "skyblue"; // 设置填充颜色
// ctx.fillRect(50, 50, 150, 100); // 绘制一个矩形
},
[calcPoints, xEndPx, xStartPx, yStartPx, yStepPx]
);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext("2d");
if (!context) return;
draw(context);
}, [draw]);
return (
<div>
<canvas ref={canvasRef} width={props.width} height={props.height}></canvas>
</div>
);
}

126
src/pages/measure/components/graph/RealtimeLayer.tsx

@ -1,31 +1,103 @@
import { useCallback, useEffect, useRef } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import points from "../../../../utils/measure.json";
import { createWebSocket, sharedWsUrl } from "../../../../services/socket";
const wsClient = createWebSocket(sharedWsUrl);
console.log(sharedWsUrl);
const pointArr: { x: number; y: number }[] = [];
export default function RealtimeLayer(props: {
width: number;
height: number;
leftPadding: number;
rightPadding: number;
topPadding: number;
bottomPadding: number;
width: number;
height: number;
leftPadding: number;
rightPadding: number;
topPadding: number;
bottomPadding: number;
columns: number;
rows: number;
}) {
const canvasRef = useRef<HTMLCanvasElement | null>(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 (
<div>
<canvas ref={canvasRef} width={props.width} height={props.height}></canvas>
</div>
);
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 unitPx = xStepPx / 10;
const pointsPx = points.map(p => ({ x: p.x * unitPx, y: p.y * unitPx + 4 })); // 特意偏移
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [rtPoints, setRtPoints] = useState<{ x: number; y: number }[]>([]);
// const draw = useCallback(
// (ctx: CanvasRenderingContext2D) => {
// for (let idx = 0; idx < pointsPx.length; idx++) {
// if (idx === 0) {
// ctx.moveTo(pointsPx[idx].x, pointsPx[idx].y);
// } else {
// ctx.lineTo(pointsPx[idx].x, pointsPx[idx].y);
// }
// }
// ctx.stroke();
// },
// [pointsPx, xEndPx, xStartPx, yStartPx, yStepPx]
// );
useEffect(() => {
const subscription = wsClient.dataOb.subscribe(data => {
if (data.path === "/measurement-task/get-task-state") {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
if (data.data.event === "START_RECORD_SIG") {
// setRtPoints([]);
pointArr.length = 0;
ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
setTimeout(() => {
ctx.strokeStyle = "blue";
const xOffset = (xEndPx - xStartPx) / 2;
const yOffset = yStepPx * 2;
ctx.translate(xStartPx + xOffset, yStartPx + yOffset);
ctx.beginPath();
}, 0);
} else if (data.data.event === "END_RECORD_SIG") {
//
}
} else if (data.path === "/measurement-task/profile-record-ctrl-sig") {
// console.log(data.data);
// setRtPoints(rtPoints.concat([data.data]));
pointArr.push(data.data);
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const pointsPx = pointArr.map(p => ({ x: p.x * unitPx, y: p.y * unitPx }));
for (let idx = 0; idx < pointsPx.length; idx++) {
if (idx === 0) {
ctx.moveTo(pointsPx[idx].x, pointsPx[idx].y);
} else {
ctx.lineTo(pointsPx[idx].x, pointsPx[idx].y);
}
}
ctx.stroke();
}
});
wsClient.connect();
return () => subscription.unsubscribe();
}, [rtPoints, unitPx, xEndPx, xStartPx, yStartPx, yStepPx]);
return (
<div>
<canvas ref={canvasRef} width={props.width} height={props.height}></canvas>
</div>
);
}

59
src/pages/measure/components/graph/StandardLayer.tsx

@ -1,8 +1,6 @@
import { useCallback, useEffect, useRef } from "react";
import { calculateCircleAbove, calculateCircleBelow } from "../../../../utils";
const unitPx = 8;
const pointsR: [number, number][] = [
[0, 0],
[9.949007022412, 0.1650166186941],
@ -21,8 +19,6 @@ const pointsL: [number, number][] = [
[-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;
@ -41,6 +37,10 @@ export default function StandardLayer(props: {
const yEndPx = props.height - props.bottomPadding;
const yStepPx = (props.height - props.topPadding - props.bottomPadding) / props.rows;
const unitPx = xStepPx / 10;
const pointsRight = pointsR.map(p => [p[0] * unitPx, p[1] * unitPx]);
const pointsLeft = pointsL.map(p => [p[0] * unitPx, p[1] * unitPx]);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const draw = useCallback(
@ -73,44 +73,44 @@ export default function StandardLayer(props: {
// 绘制标准线
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);
// console.log(param);
ctx.arc(
param.center.x,
param.center.y,
300 * unitPx,
param.anglesForCenter.startAngle,
param.anglesForCenter.endAngle
param.angles.startAngle,
param.angles.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);
// console.log(param);
ctx.arc(
param.center.x,
param.center.y,
80 * unitPx,
param.anglesForCenter.startAngle,
param.anglesForCenter.endAngle
param.angles.startAngle,
param.angles.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);
// console.log(param);
ctx.arc(
param.center.x,
param.center.y,
13 * unitPx,
param.anglesForCenter.startAngle,
param.anglesForCenter.endAngle
param.angles.startAngle,
param.angles.endAngle
);
point0 = pointsRight[3];
@ -122,15 +122,15 @@ export default function StandardLayer(props: {
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);
// console.log(param);
ctx.arc(param.center.x, param.center.y, 5 * unitPx, param.angles.startAngle, param.angles.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]);
@ -140,8 +140,8 @@ export default function StandardLayer(props: {
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);
// console.log(param);
ctx.arc(param.center.x, param.center.y, 5 * unitPx, param.angles.startAngle, param.angles.endAngle);
point0 = pointsLeft[2];
point1 = pointsLeft[3];
@ -152,45 +152,44 @@ export default function StandardLayer(props: {
point1 = pointsLeft[4];
ctx.moveTo(point0[0], point0[1]);
param = calculateCircleBelow(point0[0], point0[1], point1[0], point1[1], 13 * unitPx);
console.log(param);
// console.log(param);
ctx.arc(
param.center.x,
param.center.y,
13 * unitPx,
param.anglesForCenter.startAngle,
param.anglesForCenter.endAngle
param.angles.startAngle,
param.angles.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);
// console.log(param);
ctx.arc(
param.center.x,
param.center.y,
80 * unitPx,
param.anglesForCenter.startAngle,
param.anglesForCenter.endAngle
param.angles.startAngle,
param.angles.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);
// console.log(param);
ctx.arc(
param.center.x,
param.center.y,
300 * unitPx,
param.anglesForCenter.startAngle,
param.anglesForCenter.endAngle
param.angles.startAngle,
param.angles.endAngle
);
ctx.stroke();
// ctx.fillRect(100, 100, 150, 100); // 绘制一个矩形
},
[xEndPx, xStartPx, xStepPx, yEndPx, yStartPx, yStepPx]
[pointsLeft, pointsRight, unitPx, xEndPx, xStartPx, xStepPx, yEndPx, yStartPx, yStepPx]
);
useEffect(() => {
const canvas = canvasRef.current;

33
src/services/socket.ts

@ -1,6 +1,7 @@
import { Subject } from "rxjs";
import { Datagram } from "./wsTypes";
export type SocketState = 'open' | 'close' | 'error'
export type SocketState = "open" | "close" | "error";
class WebSocketClient {
private ws: WebSocket | null = null;
@ -9,9 +10,14 @@ class WebSocketClient {
private maxReconnectAttempts: number = 5;
private reconnectInterval: number = 3000;
readonly dataOb = new Subject()
readonly stateOb = new Subject<SocketState>()
private dataSub = new Subject<Datagram>();
get dataOb() {
return this.dataSub.asObservable();
}
private stateSub = new Subject<SocketState>();
get stateOb() {
return this.stateSub.asObservable();
}
constructor(url: string) {
this.url = url;
}
@ -41,28 +47,32 @@ class WebSocketClient {
this.ws.onopen = () => {
console.log("WebSocket 连接已建立");
this.reconnectAttempts = -1; // 重置重连次数
this.stateOb.next('open')
this.stateSub.next("open");
};
// 接收消息的处理
this.ws.onmessage = (event: MessageEvent) => {
try {
const data = JSON.parse(event.data);
// console.log('🚀 ~ WebSocketClient ~ bindEvents ~ data:', data)
this.dataOb.next(data)
const data = JSON.parse(event.data) as Datagram;
// console.log("🚀 ~ WebSocketClient ~ bindEvents ~ data:", data);
// if (data.type === "cmd") {
// this.dataSub.next({ type: data.type, data: { ...data.data, success: data.data.status === "D0000" } });
// } else {
this.dataSub.next(data);
// }
} catch (error) {
console.error("消息解析错误:", error);
}
};
this.ws.onclose = () => {
this.stateOb.next('close')
this.stateSub.next("close");
console.log("WebSocket 连接已关闭");
this.reconnect();
};
this.ws.onerror = error => {
this.stateOb.next('error')
this.stateSub.next("error");
console.error("WebSocket 错误:", error);
};
}
@ -107,5 +117,4 @@ export const createWebSocket = (url: string): WebSocketClient => {
}
};
export const sharedWsUrl = `ws://${process.env.REACT_APP_WS_URL}`;
export const sharedWsUrl = `ws://${process.env.REACT_APP_WS_URL}`;

20
src/services/wsTypes.ts

@ -0,0 +1,20 @@
// 开始、停止绘制
export type TaskState = {
messageType: "EVENT";
data: {
event: "START_RECORD_SIG" | "END_RECORD_SIG";
};
path: "/measurement-task/get-task-state";
};
// 连接上报坐标点
export type TrackRecordSig = {
messageType: "EVENT";
data: {
x: number;
y: number;
};
path: "/measurement-task/profile-record-ctrl-sig";
};
export type Datagram = TrackRecordSig | TaskState;

26
src/utils/index.ts

@ -4,6 +4,7 @@ export function formatRemainTime(seconds: number) {
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;
@ -41,12 +42,13 @@ export function calculateCircleInfo(x1: number, y1: number, x2: number, y2: numb
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 } },
{ center: { x: cx1, y: cy1 }, angles: { startAngle: startAngle1, endAngle: endAngle1 } },
{ center: { x: cx2, y: cy2 }, angles: { startAngle: startAngle2, endAngle: endAngle2 } },
];
}
export type CenterAndAngle = ReturnType<typeof calculateCircleBelow>;
// 取圆心偏下的那一组圆心和夹角
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) {
@ -55,6 +57,7 @@ export function calculateCircleBelow(x1: number, y1: number, x2: number, y2: num
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) {
@ -63,3 +66,22 @@ export function calculateCircleAbove(x1: number, y1: number, x2: number, y2: num
return results[1];
}
}
// 根据圆心坐标、半径和角度来计算目标点的坐标
export function calculatePointOnCircle(cx: number, cy: number, radius: number, angleInDegrees: number) {
// 将角度从度转换为弧度
const angleInRadians = angleInDegrees * (Math.PI / 180);
// 计算目标点的坐标
const x = cx + radius * Math.cos(angleInRadians);
const y = cy + radius * Math.sin(angleInRadians);
return { x, y };
}
// 计算一个点相对于原点的对称点坐标
export function findSymmetricPoint(px: number, py: number, ox = 0, oy = 0) {
// 计算对称点的坐标
const sx = 2 * ox - px;
const sy = 2 * oy - py;
return { x: sx, y: sy };
}

1503
src/utils/measure.json
File diff suppressed because it is too large
View File

Loading…
Cancel
Save